Repository: GrantChain/GrantFox
Pull request #17: Feat forgot password flow with supabase
Author: login: gchyuli
Labels: name: enhancement
description: New feature or request
name: non-rewarded
name: ODHack13
name: Maintainer - Joel Vargas
name: External Dev
Created: 2025-05-02T00:44:36Z | Merged: 2025-05-02T00:48:19Z

<p align="center"> <img src="https://github.com/user-attachments/assets/25cfd5b8-e303-4af6-920e-e42d1ac8d418" alt="CLR-S (2)"> </p>

# Pull Request | GrantChain

## 1. Issue Link

https://github.com/GrantChain/GrantFox/issues/11

- Closes #11

---

## 2. Brief Description of the Issue

Implemented a complete "Forgot Password" flow using Supabase Auth. This allows users to securely reset their password if they forget it. The flow includes a form to request the reset link, an email with a secure token, and a page where the user can set a new password.

---

## 3. Type of Change

- [ ] 📝 Documentation (updates to README, docs, or comments)  
- [ ] 🐛 Bug fix (non-breaking change which fixes an issue)  
- [x] 👌 Enhancement (non-breaking change which adds functionality)  
- [ ] 💥 Breaking change (fix or feature that would cause existing functionality to change)

---

## 4. Changes Made

- Added "Forgot Password?" link to the login page.
- Created `ForgotPasswordForm` to request a reset email.
- Integrated Supabase Auth to send a password reset email with a custom redirect URL.
- Created a `/reset-password` page to handle token-based password reset.
- Implemented `ResetPasswordForm` with secure password validation and confirmation.
- Handled expired or invalid token scenarios.
- Built responsive UI using ShadCN components, with support for dark/light mode.
- Added client-side handling for `access_token` via `useSearchParams`.

---

## 5. Evidence Before Solution

[Loom Video - Before Solution](LINK_TO_VIDEO_BEFORE)

---

## 6. Evidence After Solution

[[Loom Video - After Solution](LINK_TO_VIDEO_AFTER)](https://www.loom.com/share/f3413cf39f1f42a8943754c3cb466d7c?sid=a81cac04-49a6-4380-806f-ae46a1e73397)

---

## 7. Important Notes

- Make sure the `redirectTo` URLs are properly configured in the Supabase project for both development and production.
- `access_token` is extracted and validated in a client component using `useSearchParams`, avoiding SSR access issues.
- Fixed a compilation error by properly scoping hooks like `useRouter` and `useSearchParams` to client-only components.
- Supabase rate limits reset requests to once every 30 seconds per user for security — error handling is in place for this.


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added "Forgot Password" and "Reset Password" pages with dedicated forms and flows for password recovery.
  - Introduced UI components for requesting a password reset link and setting a new password.
  - Implemented API endpoint for handling password reset requests.
  - Integrated password reset logic using Supabase authentication.
  - Added new dependencies to support password reset and notifications.

- **Bug Fixes**
  - None.

- **Style**
  - Improved code formatting, indentation, and added semicolons for consistency across multiple files.

- **Documentation**
  - Minor formatting and whitespace adjustments in the README for improved readability.

- **Chores**
  - Added new utility modules and hooks to support password reset functionality.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #11: full stack: forgot Password Flow with Supabase
## **Issue - forgot Password Flow with Supabase**  

### Authentication & User Management  

Implement a **Forgot Password** feature using **Supabase** to allow users to reset their passwords.  

---  

### **Description**:  
Users should be able to **reset their password** if they forget it. The flow should:  
- Provide a **"Forgot Password?"** link on the login page.  
- Allow users to **enter their email** and request a reset link.  
- Send a **password reset email** using **Supabase Auth**.  
- Redirect users to a **password reset page** where they can enter a new password.  
- Ensure proper **error handling and success messages**.  

---  

### **Requirements**:  
- **Forgot Password Form**:  
  - Add a **"Forgot Password?"** link to the login page.  
  - Create a form where users can enter their **email** to request a reset link.  
- **Email Handling**:  
  - Use **Supabase Auth** to send the password reset email.  
  - Ensure the email template is properly configured in Supabase.  
- **Reset Password Page**:  
  - Create a page where users can enter a **new password** after clicking the reset link.  
  - Validate **password strength** and confirmation match.  
  - Submit the new password to **Supabase** to complete the reset.  
- **User Feedback**:  
  - Show **loading states** while processing.  
  - Display **success messages** after requesting a reset link.  
  - Handle **error messages** for invalid or expired links.  
- **Design & Responsiveness**:  
  - Use **ShadCN** components for UI.  
  - Ensure **dark and light mode** compatibility.  
  - Make the UI **fully responsive**.  

---  

### **Validations**:  
- Users can successfully **request a password reset link**.  
- The reset email is **sent correctly** via Supabase.  
- Clicking the reset link redirects to the **password reset page**.  
- Users can **set a new password** and log in successfully.  
- Proper error messages appear for **invalid or expired links**.  
- The UI works correctly in **dark/light mode** and is **responsive**.  

---  

### **Tests**:  
- Enter a valid email and verify that the **reset email is sent**.  
- Click the reset link and confirm it leads to the **password reset page**.  
- Try submitting a **weak password** and verify validation errors.  
- Set a **valid new password** and ensure login works afterward.  
- Test error handling for **invalid or expired reset links**.  
- Check UI responsiveness on **mobile, tablet, and desktop**.  
- Test both **light and dark mode** for visual consistency.  

Linked issue comments: user: login: gchyuli
created_at: 2025-04-26T19:50:15Z
body: Hi, I’m Yuliana Gonzalez and I’d love to work on this issue!  
I’m a Dojo Coding member ⚩️ with strong experience in frontend development with React, TypeScript, Tailwind CSS, shadcn/ui, and Supabase Authentication.  

---

### 🧠 My Approach to Solving This Issue

I’ll implement the forgot password flow carefully by combining a clean UI, smooth Supabase integration, and detailed user feedback for the best experience across devices and themes.

---

### Phase 1: Forgot Password Form Setup

- Add a "Forgot Password?" link to the existing login page.
- Create a dedicated form for users to input their email address.
- Use shadcn/ui components for the form fields, buttons, and feedback messages.

---

### Phase 2: Email Handling via Supabase Auth

- Use Supabase's `resetPasswordForEmail` method to send password reset emails.
- Verify that Supabase email templates are properly configured.
- Implement loading indicators during request processing.
- Display success or error messages clearly after email request.

---

### Phase 3: Reset Password Page

- Create a `ResetPassword` page that users are redirected to via the email reset link.
- Include fields for:
  - New password input
  - Password confirmation input
- Validate password strength (minimum length, complexity if needed).
- Submit the new password securely to Supabase to complete the password reset.

---

### Phase 4: User Feedback & Error Handling

- Show loading states during operations.
- Display clear success messages after reset completion.
- Handle errors like:
  - Expired or invalid reset links
  - Weak passwords
- Redirect users back to the login page after successful password reset.

---

### Phase 5: UI Design & Responsiveness

- Style all forms and pages using shadcn/ui components.
- Ensure full compatibility with light and dark mode.
- Make layouts fully responsive for mobile, tablet, and desktop viewports.
- Maintain high accessibility standards (keyboard navigation, ARIA labels).

---

### Phase 6: Testing & Validation

- Test flow end-to-end:
  - Request password reset with valid email.
  - Click reset link and land on Reset Password page.
  - Submit new password and verify successful login.
- Test edge cases:
  - Weak passwords show validation errors.
  - Invalid/expired reset links show error states.
- Verify responsiveness and theme consistency across all devices.

---

I’m excited to bring this important user flow to life with extra care for UX, security, and accessibility! Let me know if I’m good to start 🚀

author_association: CONTRIBUTOR
user: login: JoelVR17
created_at: 2025-05-01T17:51:03Z
body: Hi @gchyuli, How is your progress on the issue?

Only Dust requires to merge the PR on Monday and Tuesday. 
author_association: CONTRIBUTOR

Diff:
diff --git a/README.md b/README.md
index 48f9f9d..594457e 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ Follow the steps below to get started with this project:
 
 1. Install npm dependencies.
 2. Create a Supabase Database. [Tutorial](https://youtu.be/jA2-IwR0zjk)
-3. Prisma Migration. This video works too👆🏼      
+3. Prisma Migration. This video works too👆🏼
 4. Run the Project.
 
 ## Installation
@@ -69,7 +69,7 @@ Follow the steps below to get started with this project:
 
 3. Generate the Prisma Model into Supabase
 
-    ```bash
+   ```bash
    npx prisma db push
    ```
 
@@ -104,7 +104,6 @@ NEXT_PUBLIC_API_KEY=
 
 https://github.com/user-attachments/assets/31fd6423-fab8-4dfd-96ac-077cd537cb22
 
-
 ## Wallet Requirements
 
 To use this project, you must have one of the following wallets installed:
diff --git a/package-lock.json b/package-lock.json
index 5fc3aa3..233612a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -29,6 +29,7 @@
         "class-variance-authority": "^0.7.1",
         "clsx": "^2.1.1",
         "framer-motion": "^12.9.2",
+        "gig-fox": "file:",
         "i": "^0.3.7",
         "lucide-react": "^0.481.0",
         "next": "15.1.7",
@@ -37,6 +38,7 @@
         "react": "^19.0.0",
         "react-dom": "^19.0.0",
         "react-hook-form": "^7.54.2",
+        "sonner": "^2.0.3",
         "tailwind-merge": "^3.0.2",
         "tailwindcss-animate": "^1.0.7",
         "zod": "^3.24.2",
@@ -10425,6 +10427,10 @@
         "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
       }
     },
+    "node_modules/gig-fox": {
+      "resolved": "",
+      "link": true
+    },
     "node_modules/glob": {
       "version": "10.4.5",
       "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz",
@@ -16298,6 +16304,16 @@
         "atomic-sleep": "^1.0.0"
       }
     },
+    "node_modules/sonner": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.3.tgz",
+      "integrity": "sha512-njQ4Hht92m0sMqqHVDL32V2Oun9W1+PHO9NDv9FHfJjT3JT22IG4Jpo3FPQy+mouRKCXFWO+r67v6MrHX2zeIA==",
+      "license": "MIT",
+      "peerDependencies": {
+        "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc",
+        "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+      }
+    },
     "node_modules/source-map-js": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
diff --git a/package.json b/package.json
index 12a9e31..03841d9 100644
--- a/package.json
+++ b/package.json
@@ -30,6 +30,7 @@
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "framer-motion": "^12.9.2",
+    "gig-fox": "file:",
     "i": "^0.3.7",
     "lucide-react": "^0.481.0",
     "next": "15.1.7",
@@ -38,6 +39,7 @@
     "react": "^19.0.0",
     "react-dom": "^19.0.0",
     "react-hook-form": "^7.54.2",
+    "sonner": "^2.0.3",
     "tailwind-merge": "^3.0.2",
     "tailwindcss-animate": "^1.0.7",
     "zod": "^3.24.2",
diff --git a/src/app/(auth)/forgot-password/page.tsx b/src/app/(auth)/forgot-password/page.tsx
new file mode 100644
index 0000000..333ebd5
--- /dev/null
+++ b/src/app/(auth)/forgot-password/page.tsx
@@ -0,0 +1,19 @@
+import { Metadata } from "next";
+import { ForgotPasswordForm } from "@/components/modules/auth/ui/forms/ForgotPassword";
+
+export const metadata: Metadata = {
+  title: "Forgot Password - GrantFox",
+  description: "Request a password reset for your GrantFox account",
+};
+
+export default function ForgotPasswordPage() {
+  return (
+    <div className="container flex h-screen w-screen flex-col items-center justify-center">
+      <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
+        <div className="flex flex-col space-y-2 text-center">
+        </div>
+            <ForgotPasswordForm />
+      </div>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/src/app/(auth)/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx
new file mode 100644
index 0000000..1e9850f
--- /dev/null
+++ b/src/app/(auth)/reset-password/page.tsx
@@ -0,0 +1,12 @@
+import { Metadata } from "next";
+import { ResetPasswordClient } from "@/components/modules/auth/ui/pages/ResetPasswordClient";
+
+export const metadata: Metadata = {
+  title: "Reset Password - GrantFox",
+  description: "Set a new password for your GrantFox account",
+};
+
+export default function ResetPasswordPage() {
+    console.log("Page loaded");
+    return <ResetPasswordClient />;
+}
diff --git a/src/app/api/(auth)/forgot-password/route.ts b/src/app/api/(auth)/forgot-password/route.ts
new file mode 100644
index 0000000..a890943
--- /dev/null
+++ b/src/app/api/(auth)/forgot-password/route.ts
@@ -0,0 +1,41 @@
+import { NextResponse } from "next/server";
+import { z } from "zod";
+import { createServerClient } from "@/lib/supabase-server";
+
+// Define schema for validating email input
+const ForgotPasswordSchema = z.object({
+  email: z.string().email(),
+});
+
+export async function POST(req: Request) {
+  const body = await req.json();
+  const parsed = ForgotPasswordSchema.safeParse(body);
+
+  // If the email is invalid, return 400
+  if (!parsed.success) {
+    return NextResponse.json({ error: "Invalid email address" }, { status: 400 });
+  }
+
+  const { email } = parsed.data;
+
+  try {
+    // Create a Supabase server client
+    const supabase = createServerClient();
+
+    // Send password reset email with redirect URL
+    const { error } = await supabase.auth.resetPasswordForEmail(email, {
+      redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/reset-password`,
+    });
+
+    if (error) {
+      // Return any Supabase-related error
+      return NextResponse.json({ error: error.message }, { status: 500 });
+    }
+
+    // Return success response
+    return NextResponse.json({ message: "Password reset email sent" }, { status: 200 });
+  } catch (error) {
+    // Catch unexpected errors
+    return NextResponse.json({ error: "Server error" }, { status: 500 });
+  }
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index 50a2323..e596a60 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -30,13 +30,11 @@ export default function RootLayout({
       <body
         className={`${geistSans.variable} ${geistMono.variable} antialiased`}
       >
-        <ThemeProvider
-          defaultTheme="system"
-        >
+        <ThemeProvider defaultTheme="system">
           <TanstackProvider>{children}</TanstackProvider>
           <Toaster />
         </ThemeProvider>
       </body>
     </html>
   );
-}
\ No newline at end of file
+}
diff --git a/src/components/layout/sidebar/app-sidebar.tsx b/src/components/layout/sidebar/app-sidebar.tsx
index 4f13b87..1baa996 100644
--- a/src/components/layout/sidebar/app-sidebar.tsx
+++ b/src/components/layout/sidebar/app-sidebar.tsx
@@ -8,7 +8,7 @@ import {
   Command,
   Frame,
   GalleryVerticalEnd,
-  // biome-ignore lint/suspicious/noShadowRestrictedNames: <Map is a valid name but you need to be careful with it, as is also reserved in JS> 
+  // biome-ignore lint/suspicious/noShadowRestrictedNames: <Map is a valid name but you need to be careful with it, as is also reserved in JS>
   Map,
   PieChart,
   Settings2,
@@ -180,4 +180,4 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
       <SidebarRail />
     </Sidebar>
   );
-}
\ No newline at end of file
+}
diff --git a/src/components/layout/sidebar/theme-toggler-switch.tsx b/src/components/layout/sidebar/theme-toggler-switch.tsx
index d9a40f0..b4a332b 100644
--- a/src/components/layout/sidebar/theme-toggler-switch.tsx
+++ b/src/components/layout/sidebar/theme-toggler-switch.tsx
@@ -7,7 +7,7 @@ import { Switch } from "@/components/ui/switch";
 
 export function ThemeToggleSwitch() {
   const { theme, setTheme } = useTheme();
-  
+
   const toggleTheme = () => {
     setTheme(theme === "dark" ? "light" : "dark");
   };
@@ -23,4 +23,4 @@ export function ThemeToggleSwitch() {
       <Moon className="h-4 w-4" />
     </div>
   );
-}
\ No newline at end of file
+}
diff --git a/src/components/layout/sidebar/theme-toggler.tsx b/src/components/layout/sidebar/theme-toggler.tsx
index 110926e..b02c1f8 100644
--- a/src/components/layout/sidebar/theme-toggler.tsx
+++ b/src/components/layout/sidebar/theme-toggler.tsx
@@ -5,37 +5,37 @@ import { Moon, Sun } from "lucide-react";
 
 import { Button } from "@/components/ui/button";
 import {
-	DropdownMenu,
-	DropdownMenuContent,
-	DropdownMenuItem,
-	DropdownMenuTrigger,
+  DropdownMenu,
+  DropdownMenuContent,
+  DropdownMenuItem,
+  DropdownMenuTrigger,
 } from "@/components/ui/dropdown-menu";
 import { useTheme } from "@/hooks/theme-provider";
 
 export function ThemeToggle() {
-	// eslint-disable-next-line @typescript-eslint/no-unused-vars
-	const { theme, setTheme } = useTheme();
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const { theme, setTheme } = useTheme();
 
-	return (
-		<DropdownMenu>
-			<DropdownMenuTrigger asChild>
-				<Button variant="ghost" size="icon" className="h-9 w-9">
-					<Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
-					<Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
-					<span className="sr-only">Toggle theme</span>
-				</Button>
-			</DropdownMenuTrigger>
-			<DropdownMenuContent align="end">
-				<DropdownMenuItem onClick={() => setTheme("light")}>
-					Light
-				</DropdownMenuItem>
-				<DropdownMenuItem onClick={() => setTheme("dark")}>
-					Dark
-				</DropdownMenuItem>
-				<DropdownMenuItem onClick={() => setTheme("system")}>
-					System
-				</DropdownMenuItem>
-			</DropdownMenuContent>
-		</DropdownMenu>
-	);
+  return (
+    <DropdownMenu>
+      <DropdownMenuTrigger asChild>
+        <Button variant="ghost" size="icon" className="h-9 w-9">
+          <Sun className="h-4 w-4 rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
+          <Moon className="absolute h-4 w-4 rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
+          <span className="sr-only">Toggle theme</span>
+        </Button>
+      </DropdownMenuTrigger>
+      <DropdownMenuContent align="end">
+        <DropdownMenuItem onClick={() => setTheme("light")}>
+          Light
+        </DropdownMenuItem>
+        <DropdownMenuItem onClick={() => setTheme("dark")}>
+          Dark
+        </DropdownMenuItem>
+        <DropdownMenuItem onClick={() => setTheme("system")}>
+          System
+        </DropdownMenuItem>
+      </DropdownMenuContent>
+    </DropdownMenu>
+  );
 }
diff --git a/src/components/modules/auth/ui/forms/ForgotPassword.tsx b/src/components/modules/auth/ui/forms/ForgotPassword.tsx
new file mode 100644
index 0000000..01a4c29
--- /dev/null
+++ b/src/components/modules/auth/ui/forms/ForgotPassword.tsx
@@ -0,0 +1,124 @@
+"use client";
+
+import { useState } from "react";
+import { cn } from "@/lib/utils";
+import { Button } from "@/components/ui/button";
+import { Card, CardContent } from "@/components/ui/card";
+import { Input } from "@/components/ui/input";
+import {
+  FormControl,
+  FormField,
+  FormItem,
+  FormLabel,
+  FormMessage,
+} from "@/components/ui/form";
+import { FormProvider } from "react-hook-form";
+import Link from "next/link";
+import { ArrowLeft } from "lucide-react";
+import { usePasswordReset } from "@/hooks/password-reset.hook";
+
+export function ForgotPasswordForm({
+  className,
+  ...props
+}: React.ComponentProps<"div">) {
+  const { form, isLoading, isSuccess, handleRequestReset } = usePasswordReset();
+
+  return (
+    <div className={cn("flex flex-col gap-6", className)} {...props}>
+      <Card className="overflow-hidden">
+        <CardContent className="grid p-0 grid-cols-1">
+          <FormProvider {...form}>
+            <form
+              onSubmit={form.handleSubmit(handleRequestReset)}
+              className="p-6 md:p-8"
+            >
+              <div className="flex flex-col gap-6">
+                <div className="flex flex-col items-center text-center">
+                  <h1 className="text-2xl font-bold">Reset your password</h1>
+                  <p className="text-balance text-muted-foreground">
+                    Enter your email to receive a password reset link
+                  </p>
+                </div>
+
+                {isSuccess ? (
+                  <div className="flex flex-col gap-4 items-center text-center">
+                    <div className="rounded-full bg-green-100 p-2 dark:bg-green-900">
+                      <svg
+                        xmlns="http://www.w3.org/2000/svg"
+                        className="h-6 w-6 text-green-600 dark:text-green-300"
+                        fill="none"
+                        viewBox="0 0 24 24"
+                        stroke="currentColor"
+                      >
+                        <path
+                          strokeLinecap="round"
+                          strokeLinejoin="round"
+                          strokeWidth={2}
+                          d="M5 13l4 4L19 7"
+                        />
+                      </svg>
+                    </div>
+                    <p className="text-sm text-muted-foreground">
+                      If an account exists with that email, we've sent a password reset link.
+                      Please check your inbox and spam folder.
+                    </p>
+                  </div>
+                ) : (
+                  <div className="grid gap-2">
+                    <FormField
+                      control={form.control}
+                      name="email"
+                      render={({ field }) => (
+                        <FormItem>
+                          <FormLabel className="flex items-center">
+                            Email
+                            <span className="text-destructive ml-1">*</span>
+                          </FormLabel>
+                          <FormControl>
+                            <Input
+                              placeholder="Enter email"
+                              type="email"
+                              {...field}
+                              onChange={(e) => {
+                                field.onChange(e);
+                              }}
+                            />
+                          </FormControl>
+                          <FormMessage />
+                        </FormItem>
+                      )}
+                    />
+                  </div>
+                )}
+
+                {!isSuccess && (
+                  <Button
+                    type="submit"
+                    className="w-full"
+                    disabled={isLoading}
+                  >
+                    {isLoading ? "Sending..." : "Send Reset Link"}
+                  </Button>
+                )}
+
+                <div className="flex justify-center">
+                  <Link
+                    href="/login"
+                    className="flex items-center text-sm text-muted-foreground hover:text-foreground"
+                  >
+                    <ArrowLeft size={16} className="mr-1" />
+                    Back to Login
+                  </Link>
+                </div>
+              </div>
+            </form>
+          </FormProvider>
+        </CardContent>
+      </Card>
+      <div className="text-balance text-center text-xs text-muted-foreground [&_a]:underline [&_a]:underline-offset-4 hover:[&_a]:text-primary">
+        By clicking continue, you agree to our <a href="#">Terms of Service</a>{" "}
+        and <a href="#">Privacy Policy</a>.
+      </div>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/src/components/modules/auth/ui/forms/LoginForm.tsx b/src/components/modules/auth/ui/forms/LoginForm.tsx
index 6ee2a89..ff052a5 100644
--- a/src/components/modules/auth/ui/forms/LoginForm.tsx
+++ b/src/components/modules/auth/ui/forms/LoginForm.tsx
@@ -109,6 +109,12 @@ export function LoginForm({
                     Sign up
                   </Link>
                 </div>
+          
+                <div className="text-center text-sm">
+                <Link href="/forgot-password" className="underline underline-offset-4">
+                  Forgot your password?
+                </Link>
+              </div>
               </div>
             </form>
           </FormProvider>
diff --git a/src/components/modules/auth/ui/forms/ResetPasswordForm.tsx b/src/components/modules/auth/ui/forms/ResetPasswordForm.tsx
new file mode 100644
index 0000000..c8d718f
--- /dev/null
+++ b/src/components/modules/auth/ui/forms/ResetPasswordForm.tsx
@@ -0,0 +1,209 @@
+"use client";
+
+import { usePasswordReset } from "@/hooks/password-reset.hook";
+import { useForm, FormProvider } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import * as z from "zod";
+
+import {
+  CardHeader,
+  CardTitle,
+  Card,
+  CardContent,
+} from "@/components/ui/card";
+import {
+  FormField,
+  FormItem,
+  FormLabel,
+  FormControl,
+  FormMessage,
+} from "@/components/ui/form";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import { Eye, EyeOff } from "lucide-react";
+import { useState, useEffect } from "react";
+
+// Password validation schema with simplified requirements
+// This ensures passwords meet security standards with simple error messages
+const passwordSchema = z
+  .object({
+    password: z
+      .string()
+      .min(8, "Password must be at least 8 characters")
+      .regex(/[A-Z]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[a-z]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[0-9]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[^A-Za-z0-9]/, "Password must include uppercase, lowercase, number and special character"),
+    confirmPassword: z.string(),
+  })
+  // Additional validation to ensure both passwords match
+  .refine((data) => data.password === data.confirmPassword, {
+    message: "Passwords do not match",
+    path: ["confirmPassword"],
+  });
+
+// TypeScript type derived from the zod schema
+type PasswordFormData = z.infer<typeof passwordSchema>;
+
+export function ResetPasswordForm() {
+  // Custom hook that provides password reset functionality and state management
+  const {
+    isLoading,
+    isSuccess,
+    error,
+    handlePasswordReset,
+    showPassword,
+    showConfirmPassword,
+    setShowPassword,
+    setShowConfirmPassword,
+  } = usePasswordReset();
+
+  // State to track if password meets minimum length
+  const [isPasswordValid, setIsPasswordValid] = useState(true);
+  // State to track if passwords match
+  const [passwordsMatch, setPasswordsMatch] = useState(true);
+
+  // Initialize form with validation schema and default values
+  const methods = useForm<PasswordFormData>({
+    resolver: zodResolver(passwordSchema),
+    defaultValues: {
+      password: "",
+      confirmPassword: "",
+    },
+    mode: "onChange",
+  });
+
+  // Watch for changes in password and confirmPassword
+  useEffect(() => {
+    const subscription = methods.watch((value, { name }) => {
+      // Check password length
+      if (name === "password" || name === undefined) {
+        const password = value.password || "";
+        setIsPasswordValid(password.length === 0 || password.length >= 8);
+      }
+      
+      // Check if passwords match, but only if confirm field has content
+      if ((name === "confirmPassword" || name === "password" || name === undefined) && 
+          value.confirmPassword && value.confirmPassword.length > 0) {
+        setPasswordsMatch(value.password === value.confirmPassword);
+      }
+    });
+    
+    return () => subscription.unsubscribe();
+  }, [methods]);
+
+  // Form submission handler that calls the password reset function
+  const onSubmit = methods.handleSubmit((data) => {
+    handlePasswordReset(data);
+  });
+
+  // Show success message and login button when password reset is successful
+  if (isSuccess) {
+    return (
+      <div className="flex items-center justify-center min-h-[60vh]">
+        <Card className="w-full max-w-md text-center">
+          <CardHeader>
+            <CardTitle className="text-2xl">Password Reset</CardTitle>
+          </CardHeader>
+          <CardContent className="flex flex-col items-center gap-6">
+            <p className="text-muted-foreground">
+              Your password has been successfully reset!
+            </p>
+            <Button onClick={() => (window.location.href = "/login")}>
+              Log in
+            </Button>
+          </CardContent>
+        </Card>
+      </div>
+    );
+  }
+
+  // Main form rendering with password fields and validation
+  return (
+    <div className="flex flex-col gap-6">
+      <Card className="overflow-hidden">
+        <CardContent className="p-0">
+          <FormProvider {...methods}>
+            <form onSubmit={onSubmit} className="p-6 md:p-8">
+              {/* Form header with title and description */}
+              <div className="flex flex-col items-center text-center mb-6">
+                <h1 className="text-2xl font-bold">Reset your password</h1>
+                <p className="text-muted-foreground text-sm">Choose a new secure password</p>
+              </div>
+
+              {/* Display any error messages from the API */}
+              {error && <p className="text-sm text-red-500 mb-4">{error}</p>}
+
+              <div className="grid gap-4">
+                {/* New password input field with show/hide toggle */}
+                <FormField
+                  control={methods.control}
+                  name="password"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>New Password</FormLabel>
+                      <FormControl>
+                        <div className="relative">
+                          <Input
+                            type={showPassword ? "text" : "password"}
+                            {...field}
+                            className="pr-10"
+                          />
+                          {/* Toggle button to show/hide password */}
+                          <button
+                            type="button"
+                            className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground"
+                            onClick={() => setShowPassword((prev) => !prev)}
+                          >
+                            {showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
+                          </button>
+                        </div>
+                      </FormControl>
+                    
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                {/* Confirm password input field with show/hide toggle */}
+                <FormField
+                  control={methods.control}
+                  name="confirmPassword"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>Confirm Password</FormLabel>
+                      <FormControl>
+                        <div className="relative">
+                          <Input
+                            type={showConfirmPassword ? "text" : "password"}
+                            {...field}
+                            className="pr-10"
+                          />
+                          {/* Toggle button to show/hide confirmed password */}
+                          <button
+                            type="button"
+                            className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground"
+                            onClick={() => setShowConfirmPassword((prev) => !prev)}
+                          >
+                            {showConfirmPassword ? <EyeOff size={18} /> : <Eye size={18} />}
+                          </button>
+                        </div>
+                      </FormControl>
+                      
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                {/* Submit button with loading state */}
+                <Button type="submit" className="w-full" disabled={isLoading}>
+                  {isLoading ? "Updating..." : "Update Password"}
+                </Button>
+              </div>
+            </form>
+          </FormProvider>
+        </CardContent>
+      </Card>
+    </div>
+  );
+}
\ No newline at end of file
diff --git a/src/components/modules/auth/ui/pages/ResetPasswordClient.tsx b/src/components/modules/auth/ui/pages/ResetPasswordClient.tsx
new file mode 100644
index 0000000..e95280e
--- /dev/null
+++ b/src/components/modules/auth/ui/pages/ResetPasswordClient.tsx
@@ -0,0 +1,69 @@
+"use client";
+
+import { useSearchParams, useRouter } from "next/navigation";
+import { useEffect, useState } from "react";
+import { ResetPasswordForm } from "../forms/ResetPasswordForm";
+import { supabase } from "@/lib/supabase";
+import {
+  CardHeader,
+  CardTitle,
+  Card,
+  CardContent,
+} from "@/components/ui/card";
+
+export function ResetPasswordClient() {
+  const searchParams = useSearchParams();
+  const router = useRouter();
+  const [isValidToken, setIsValidToken] = useState<boolean | null>(null);
+  
+  // Supabase handles authentication through the full URL, not just a parameter
+  useEffect(() => {
+    const handleHashChange = async () => {
+      // Check if Supabase can recover the session from the URL
+      const { data, error } = await supabase.auth.getSession();
+      
+      if (error || !data.session) {
+        console.error("Error validating reset token:", error);
+        setIsValidToken(false);
+        router.replace("/forgot-password?error=invalid_link");
+        return;
+      }
+      
+      setIsValidToken(true);
+    };
+
+    // Process the current URL
+    handleHashChange();
+  }, [router]);
+
+  // Show a loader while validating the token
+  if (isValidToken === null) {
+    return (
+      <div className="flex items-center justify-center min-h-[60vh]">
+      <Card className="w-full max-w-md text-center">
+        <CardHeader>
+          <CardTitle className="text-2xl">Validating link...</CardTitle>
+        </CardHeader>
+        <CardContent className="flex flex-col items-center gap-6">
+          <p className="text-muted-foreground">
+            Please don't recharge the page
+          </p>
+        </CardContent>
+      </Card>
+    </div>
+    );
+  }
+
+  // If the token is invalid, render nothing (we already redirected)
+  if (isValidToken === false) return null;
+
+  return (
+    <div className="container flex h-screen w-screen flex-col items-center justify-center">
+      <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
+        <div className="flex flex-col space-y-2 text-center">
+        </div>
+        <ResetPasswordForm />
+      </div>
+    </div>
+  );
+}
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
index ad1ae02..28bb2fe 100644
--- a/src/components/ui/form.tsx
+++ b/src/components/ui/form.tsx
@@ -1,8 +1,8 @@
-"use client"
+"use client";
 
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { Slot } from "@radix-ui/react-slot"
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
 import {
   Controller,
   FormProvider,
@@ -10,27 +10,27 @@ import {
   type ControllerProps,
   type FieldPath,
   type FieldValues,
-} from "react-hook-form"
+} from "react-hook-form";
 
-import { cn } from "@/lib/utils"
-import { Label } from "@/components/ui/label"
+import { cn } from "@/lib/utils";
+import { Label } from "@/components/ui/label";
 
-const Form = FormProvider
+const Form = FormProvider;
 
 type FormFieldContextValue<
   TFieldValues extends FieldValues = FieldValues,
-  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
 > = {
-  name: TName
-}
+  name: TName;
+};
 
 const FormFieldContext = React.createContext<FormFieldContextValue>(
-  {} as FormFieldContextValue
-)
+  {} as FormFieldContextValue,
+);
 
 const FormField = <
   TFieldValues extends FieldValues = FieldValues,
-  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
+  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
 >({
   ...props
 }: ControllerProps<TFieldValues, TName>) => {
@@ -38,21 +38,21 @@ const FormField = <
     <FormFieldContext.Provider value={{ name: props.name }}>
       <Controller {...props} />
     </FormFieldContext.Provider>
-  )
-}
+  );
+};
 
 const useFormField = () => {
-  const fieldContext = React.useContext(FormFieldContext)
-  const itemContext = React.useContext(FormItemContext)
-  const { getFieldState, formState } = useFormContext()
+  const fieldContext = React.useContext(FormFieldContext);
+  const itemContext = React.useContext(FormItemContext);
+  const { getFieldState, formState } = useFormContext();
 
-  const fieldState = getFieldState(fieldContext.name, formState)
+  const fieldState = getFieldState(fieldContext.name, formState);
 
   if (!fieldContext) {
-    throw new Error("useFormField should be used within <FormField>")
+    throw new Error("useFormField should be used within <FormField>");
   }
 
-  const { id } = itemContext
+  const { id } = itemContext;
 
   return {
     id,
@@ -61,36 +61,36 @@ const useFormField = () => {
     formDescriptionId: `${id}-form-item-description`,
     formMessageId: `${id}-form-item-message`,
     ...fieldState,
-  }
-}
+  };
+};
 
 type FormItemContextValue = {
-  id: string
-}
+  id: string;
+};
 
 const FormItemContext = React.createContext<FormItemContextValue>(
-  {} as FormItemContextValue
-)
+  {} as FormItemContextValue,
+);
 
 const FormItem = React.forwardRef<
   HTMLDivElement,
   React.HTMLAttributes<HTMLDivElement>
 >(({ className, ...props }, ref) => {
-  const id = React.useId()
+  const id = React.useId();
 
   return (
     <FormItemContext.Provider value={{ id }}>
       <div ref={ref} className={cn("space-y-2", className)} {...props} />
     </FormItemContext.Provider>
-  )
-})
-FormItem.displayName = "FormItem"
+  );
+});
+FormItem.displayName = "FormItem";
 
 const FormLabel = React.forwardRef<
   React.ElementRef<typeof LabelPrimitive.Root>,
   React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
 >(({ className, ...props }, ref) => {
-  const { error, formItemId } = useFormField()
+  const { error, formItemId } = useFormField();
 
   return (
     <Label
@@ -99,15 +99,16 @@ const FormLabel = React.forwardRef<
       htmlFor={formItemId}
       {...props}
     />
-  )
-})
-FormLabel.displayName = "FormLabel"
+  );
+});
+FormLabel.displayName = "FormLabel";
 
 const FormControl = React.forwardRef<
   React.ElementRef<typeof Slot>,
   React.ComponentPropsWithoutRef<typeof Slot>
 >(({ ...props }, ref) => {
-  const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+  const { error, formItemId, formDescriptionId, formMessageId } =
+    useFormField();
 
   return (
     <Slot
@@ -121,15 +122,15 @@ const FormControl = React.forwardRef<
       aria-invalid={!!error}
       {...props}
     />
-  )
-})
-FormControl.displayName = "FormControl"
+  );
+});
+FormControl.displayName = "FormControl";
 
 const FormDescription = React.forwardRef<
   HTMLParagraphElement,
   React.HTMLAttributes<HTMLParagraphElement>
 >(({ className, ...props }, ref) => {
-  const { formDescriptionId } = useFormField()
+  const { formDescriptionId } = useFormField();
 
   return (
     <p
@@ -138,19 +139,19 @@ const FormDescription = React.forwardRef<
       className={cn("text-[0.8rem] text-muted-foreground", className)}
       {...props}
     />
-  )
-})
-FormDescription.displayName = "FormDescription"
+  );
+});
+FormDescription.displayName = "FormDescription";
 
 const FormMessage = React.forwardRef<
   HTMLParagraphElement,
   React.HTMLAttributes<HTMLParagraphElement>
 >(({ className, children, ...props }, ref) => {
-  const { error, formMessageId } = useFormField()
-  const body = error ? String(error?.message ?? "") : children
+  const { error, formMessageId } = useFormField();
+  const body = error ? String(error?.message ?? "") : children;
 
   if (!body) {
-    return null
+    return null;
   }
 
   return (
@@ -162,9 +163,9 @@ const FormMessage = React.forwardRef<
     >
       {body}
     </p>
-  )
-})
-FormMessage.displayName = "FormMessage"
+  );
+});
+FormMessage.displayName = "FormMessage";
 
 export {
   useFormField,
@@ -175,4 +176,4 @@ export {
   FormDescription,
   FormMessage,
   FormField,
-}
+};
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
index 5f4117f..d692565 100644
--- a/src/components/ui/switch.tsx
+++ b/src/components/ui/switch.tsx
@@ -1,9 +1,9 @@
-"use client"
+"use client";
 
-import * as React from "react"
-import * as SwitchPrimitives from "@radix-ui/react-switch"
+import * as React from "react";
+import * as SwitchPrimitives from "@radix-ui/react-switch";
 
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
 
 const Switch = React.forwardRef<
   React.ElementRef<typeof SwitchPrimitives.Root>,
@@ -12,18 +12,18 @@ const Switch = React.forwardRef<
   <SwitchPrimitives.Root
     className={cn(
       "peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
-      className
+      className,
     )}
     {...props}
     ref={ref}
   >
     <SwitchPrimitives.Thumb
       className={cn(
-        "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0"
+        "pointer-events-none block h-4 w-4 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-4 data-[state=unchecked]:translate-x-0",
       )}
     />
   </SwitchPrimitives.Root>
-))
-Switch.displayName = SwitchPrimitives.Root.displayName
+));
+Switch.displayName = SwitchPrimitives.Root.displayName;
 
-export { Switch }
+export { Switch };
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
index 40ac9dd..bdb5a5e 100644
--- a/src/components/ui/toast.tsx
+++ b/src/components/ui/toast.tsx
@@ -1,13 +1,13 @@
-"use client"
+"use client";
 
-import * as React from "react"
-import * as ToastPrimitives from "@radix-ui/react-toast"
-import { cva, type VariantProps } from "class-variance-authority"
-import { X } from "lucide-react"
+import * as React from "react";
+import * as ToastPrimitives from "@radix-ui/react-toast";
+import { cva, type VariantProps } from "class-variance-authority";
+import { X } from "lucide-react";
 
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/utils";
 
-const ToastProvider = ToastPrimitives.Provider
+const ToastProvider = ToastPrimitives.Provider;
 
 const ToastViewport = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Viewport>,
@@ -17,12 +17,12 @@ const ToastViewport = React.forwardRef<
     ref={ref}
     className={cn(
       "fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]",
-      className
+      className,
     )}
     {...props}
   />
-))
-ToastViewport.displayName = ToastPrimitives.Viewport.displayName
+));
+ToastViewport.displayName = ToastPrimitives.Viewport.displayName;
 
 const toastVariants = cva(
   "group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full",
@@ -37,8 +37,8 @@ const toastVariants = cva(
     defaultVariants: {
       variant: "default",
     },
-  }
-)
+  },
+);
 
 const Toast = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Root>,
@@ -51,9 +51,9 @@ const Toast = React.forwardRef<
       className={cn(toastVariants({ variant }), className)}
       {...props}
     />
-  )
-})
-Toast.displayName = ToastPrimitives.Root.displayName
+  );
+});
+Toast.displayName = ToastPrimitives.Root.displayName;
 
 const ToastAction = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Action>,
@@ -63,12 +63,12 @@ const ToastAction = React.forwardRef<
     ref={ref}
     className={cn(
       "inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive",
-      className
+      className,
     )}
     {...props}
   />
-))
-ToastAction.displayName = ToastPrimitives.Action.displayName
+));
+ToastAction.displayName = ToastPrimitives.Action.displayName;
 
 const ToastClose = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Close>,
@@ -78,15 +78,15 @@ const ToastClose = React.forwardRef<
     ref={ref}
     className={cn(
       "absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600",
-      className
+      className,
     )}
     toast-close=""
     {...props}
   >
     <X className="h-4 w-4" />
   </ToastPrimitives.Close>
-))
-ToastClose.displayName = ToastPrimitives.Close.displayName
+));
+ToastClose.displayName = ToastPrimitives.Close.displayName;
 
 const ToastTitle = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Title>,
@@ -97,8 +97,8 @@ const ToastTitle = React.forwardRef<
     className={cn("text-sm font-semibold [&+div]:text-xs", className)}
     {...props}
   />
-))
-ToastTitle.displayName = ToastPrimitives.Title.displayName
+));
+ToastTitle.displayName = ToastPrimitives.Title.displayName;
 
 const ToastDescription = React.forwardRef<
   React.ElementRef<typeof ToastPrimitives.Description>,
@@ -109,12 +109,12 @@ const ToastDescription = React.forwardRef<
     className={cn("text-sm opacity-90", className)}
     {...props}
   />
-))
-ToastDescription.displayName = ToastPrimitives.Description.displayName
+));
+ToastDescription.displayName = ToastPrimitives.Description.displayName;
 
-type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>
+type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
 
-type ToastActionElement = React.ReactElement<typeof ToastAction>
+type ToastActionElement = React.ReactElement<typeof ToastAction>;
 
 export {
   type ToastProps,
@@ -126,4 +126,4 @@ export {
   ToastDescription,
   ToastClose,
   ToastAction,
-}
+};
diff --git a/src/components/ui/toaster.tsx b/src/components/ui/toaster.tsx
index 171beb4..364b797 100644
--- a/src/components/ui/toaster.tsx
+++ b/src/components/ui/toaster.tsx
@@ -1,6 +1,6 @@
-"use client"
+"use client";
 
-import { useToast } from "@/hooks/use-toast"
+import { useToast } from "@/hooks/use-toast";
 import {
   Toast,
   ToastClose,
@@ -8,10 +8,10 @@ import {
   ToastProvider,
   ToastTitle,
   ToastViewport,
-} from "@/components/ui/toast"
+} from "@/components/ui/toast";
 
 export function Toaster() {
-  const { toasts } = useToast()
+  const { toasts } = useToast();
 
   return (
     <ToastProvider>
@@ -27,9 +27,9 @@ export function Toaster() {
             {action}
             <ToastClose />
           </Toast>
-        )
+        );
       })}
       <ToastViewport />
     </ToastProvider>
-  )
+  );
 }
diff --git a/src/hooks/password-reset.hook.ts b/src/hooks/password-reset.hook.ts
new file mode 100644
index 0000000..2029380
--- /dev/null
+++ b/src/hooks/password-reset.hook.ts
@@ -0,0 +1,130 @@
+"use client";
+
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { supabase } from "@/lib/supabase";
+import { toast } from "sonner";
+import { useRouter } from "next/navigation";
+
+// Email validation schema
+const forgotPasswordSchema = z.object({
+  email: z.string().min(1, "Email is required").email("Please enter a valid email address"),
+});
+
+type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
+
+// Password reset validation schema
+const resetPasswordSchema = z
+  .object({
+    password: z
+      .string()
+      .min(8, "Password must be at least 8 characters")
+      .regex(
+        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
+        "Password must contain an uppercase letter, a lowercase letter, a number, and a special character"
+      ),
+    confirmPassword: z.string().min(1, "Please confirm your password"),
+  })
+  .refine((data) => data.password === data.confirmPassword, {
+    message: "Passwords don't match",
+    path: ["confirmPassword"],
+  });
+
+type ResetPasswordFormValues = z.infer<typeof resetPasswordSchema>;
+
+export function usePasswordReset() {
+  const router = useRouter();
+  const [isLoading, setIsLoading] = useState(false);
+  const [isSuccess, setIsSuccess] = useState(false);
+  const [error, setError] = useState<string | null>(null);
+  const [showPassword, setShowPassword] = useState(false);
+  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
+
+  const form = useForm<ForgotPasswordFormValues>({
+    resolver: zodResolver(forgotPasswordSchema),
+    defaultValues: { email: "" },
+  });
+
+  const resetForm = useForm<ResetPasswordFormValues>({
+    resolver: zodResolver(resetPasswordSchema),
+    defaultValues: {
+      password: "",
+      confirmPassword: "",
+    },
+  });
+
+  // Handle password reset request via email
+  const handleRequestReset = async (data: ForgotPasswordFormValues) => {
+    setIsLoading(true);
+    setError(null);
+
+    try {
+      const redirectTo = `${window.location.origin}/reset-password`;
+
+      const { error } = await supabase.auth.resetPasswordForEmail(data.email, {
+        redirectTo,
+      });
+
+      if (error) {
+        if (error.message.includes("30 seconds")) {
+          toast.info("Please wait 30 seconds before trying again.");
+          return;
+        }
+        throw error;
+      }
+
+      setIsSuccess(true);
+      toast.success("An email with instructions to reset your password has been sent");
+    } catch (err: any) {
+      console.error("Password reset request error:", err.message);
+      setError(err.message || "Failed to send reset email");
+      toast.error("Failed to send reset email");
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  // Handle new password submission - modified to not require explicit token
+  const handlePasswordReset = async (data: ResetPasswordFormValues) => {
+    setIsLoading(true);
+    setError(null);
+
+    try {
+      // Supabase handles the token internally via the current session
+      const { error } = await supabase.auth.updateUser({
+        password: data.password,
+      });
+
+      if (error) throw error;
+
+      setIsSuccess(true);
+      // toast.success("Password updated successfully");
+
+      // Optional: redirect after a short delay
+      setTimeout(() => {
+        router.push("/login");
+      }, 3000);
+    } catch (err: any) {
+      setError(err.message || "Failed to reset password");
+      toast.error("Failed to reset password");
+    } finally {
+      setIsLoading(false);
+    }
+  };
+
+  return {
+    form,
+    resetForm,
+    isLoading,
+    isSuccess,
+    error,
+    showPassword,
+    showConfirmPassword,
+    setShowPassword,
+    setShowConfirmPassword,
+    handleRequestReset,
+    handlePasswordReset,
+  };
+}
diff --git a/src/hooks/theme-provider.tsx b/src/hooks/theme-provider.tsx
index e6f89d2..77508ac 100644
--- a/src/hooks/theme-provider.tsx
+++ b/src/hooks/theme-provider.tsx
@@ -6,80 +6,80 @@ import { createContext, useContext, useEffect, useState } from "react";
 type Theme = "dark" | "light" | "system";
 
 type ThemeProviderProps = {
-	children: React.ReactNode;
-	defaultTheme?: Theme;
-	storageKey?: string;
+  children: React.ReactNode;
+  defaultTheme?: Theme;
+  storageKey?: string;
 };
 
 type ThemeProviderState = {
-	theme: Theme;
-	setTheme: (theme: Theme) => void;
+  theme: Theme;
+  setTheme: (theme: Theme) => void;
 };
 
 const initialState: ThemeProviderState = {
-	theme: "system",
-	setTheme: () => null,
+  theme: "system",
+  setTheme: () => null,
 };
 
 const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
 
 export function ThemeProvider({
-	children,
-	defaultTheme = "system",
-	storageKey = "theme",
-	...props
+  children,
+  defaultTheme = "system",
+  storageKey = "theme",
+  ...props
 }: ThemeProviderProps) {
-	const [theme, setTheme] = useState<Theme>(defaultTheme);
-
-	useEffect(() => {
-		const savedTheme = localStorage.getItem(storageKey);
-
-		if (savedTheme && ["dark", "light", "system"].includes(savedTheme)) {
-			setTheme(savedTheme as Theme);
-		} else {
-			setTheme(defaultTheme);
-		}
-	}, [storageKey, defaultTheme]);
-
-	useEffect(() => {
-		const root = window.document.documentElement;
-
-		root.classList.remove("light", "dark");
-
-		if (theme === "system") {
-			const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
-				.matches
-				? "dark"
-				: "light";
-
-			root.classList.add(systemTheme);
-			return;
-		}
-
-		root.classList.add(theme);
-	}, [theme]);
-
-	const value = {
-		theme,
-		setTheme: (theme: Theme) => {
-			localStorage.setItem(storageKey, theme);
-			setTheme(theme);
-		},
-	};
-
-	return (
-		<ThemeProviderContext.Provider {...props} value={value}>
-			{children}
-		</ThemeProviderContext.Provider>
-	);
+  const [theme, setTheme] = useState<Theme>(defaultTheme);
+
+  useEffect(() => {
+    const savedTheme = localStorage.getItem(storageKey);
+
+    if (savedTheme && ["dark", "light", "system"].includes(savedTheme)) {
+      setTheme(savedTheme as Theme);
+    } else {
+      setTheme(defaultTheme);
+    }
+  }, [storageKey, defaultTheme]);
+
+  useEffect(() => {
+    const root = window.document.documentElement;
+
+    root.classList.remove("light", "dark");
+
+    if (theme === "system") {
+      const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
+        .matches
+        ? "dark"
+        : "light";
+
+      root.classList.add(systemTheme);
+      return;
+    }
+
+    root.classList.add(theme);
+  }, [theme]);
+
+  const value = {
+    theme,
+    setTheme: (theme: Theme) => {
+      localStorage.setItem(storageKey, theme);
+      setTheme(theme);
+    },
+  };
+
+  return (
+    <ThemeProviderContext.Provider {...props} value={value}>
+      {children}
+    </ThemeProviderContext.Provider>
+  );
 }
 
 export const useTheme = () => {
-	const context = useContext(ThemeProviderContext);
+  const context = useContext(ThemeProviderContext);
 
-	if (context === undefined) {
-		throw new Error("useTheme must be used within a ThemeProvider");
-	}
+  if (context === undefined) {
+    throw new Error("useTheme must be used within a ThemeProvider");
+  }
 
-	return context;
+  return context;
 };
diff --git a/src/hooks/use-toast.ts b/src/hooks/use-toast.ts
index 02e111d..6555e79 100644
--- a/src/hooks/use-toast.ts
+++ b/src/hooks/use-toast.ts
@@ -1,78 +1,75 @@
-"use client"
+"use client";
 
 // Inspired by react-hot-toast library
-import * as React from "react"
+import * as React from "react";
 
-import type {
-  ToastActionElement,
-  ToastProps,
-} from "@/components/ui/toast"
+import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
 
-const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
+const TOAST_LIMIT = 1;
+const TOAST_REMOVE_DELAY = 1000000;
 
 type ToasterToast = ToastProps & {
-  id: string
-  title?: React.ReactNode
-  description?: React.ReactNode
-  action?: ToastActionElement
-}
+  id: string;
+  title?: React.ReactNode;
+  description?: React.ReactNode;
+  action?: ToastActionElement;
+};
 
 const actionTypes = {
   ADD_TOAST: "ADD_TOAST",
   UPDATE_TOAST: "UPDATE_TOAST",
   DISMISS_TOAST: "DISMISS_TOAST",
   REMOVE_TOAST: "REMOVE_TOAST",
-} as const
+} as const;
 
-let count = 0
+let count = 0;
 
 function genId() {
-  count = (count + 1) % Number.MAX_SAFE_INTEGER
-  return count.toString()
+  count = (count + 1) % Number.MAX_SAFE_INTEGER;
+  return count.toString();
 }
 
-type ActionType = typeof actionTypes
+type ActionType = typeof actionTypes;
 
 type Action =
   | {
-      type: ActionType["ADD_TOAST"]
-      toast: ToasterToast
+      type: ActionType["ADD_TOAST"];
+      toast: ToasterToast;
     }
   | {
-      type: ActionType["UPDATE_TOAST"]
-      toast: Partial<ToasterToast>
+      type: ActionType["UPDATE_TOAST"];
+      toast: Partial<ToasterToast>;
     }
   | {
-      type: ActionType["DISMISS_TOAST"]
-      toastId?: ToasterToast["id"]
+      type: ActionType["DISMISS_TOAST"];
+      toastId?: ToasterToast["id"];
     }
   | {
-      type: ActionType["REMOVE_TOAST"]
-      toastId?: ToasterToast["id"]
-    }
+      type: ActionType["REMOVE_TOAST"];
+      toastId?: ToasterToast["id"];
+    };
 
 interface State {
-  toasts: ToasterToast[]
+  toasts: ToasterToast[];
 }
 
-const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>()
+const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
 
 const addToRemoveQueue = (toastId: string) => {
   if (toastTimeouts.has(toastId)) {
-    return
+    return;
   }
 
   const timeout = setTimeout(() => {
-    toastTimeouts.delete(toastId)
+    toastTimeouts.delete(toastId);
     dispatch({
       type: "REMOVE_TOAST",
       toastId: toastId,
-    })
-  }, TOAST_REMOVE_DELAY)
+    });
+  }, TOAST_REMOVE_DELAY);
 
-  toastTimeouts.set(toastId, timeout)
-}
+  toastTimeouts.set(toastId, timeout);
+};
 
 export const reducer = (state: State, action: Action): State => {
   switch (action.type) {
@@ -80,27 +77,27 @@ export const reducer = (state: State, action: Action): State => {
       return {
         ...state,
         toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
-      }
+      };
 
     case "UPDATE_TOAST":
       return {
         ...state,
         toasts: state.toasts.map((t) =>
-          t.id === action.toast.id ? { ...t, ...action.toast } : t
+          t.id === action.toast.id ? { ...t, ...action.toast } : t,
         ),
-      }
+      };
 
     case "DISMISS_TOAST": {
-      const { toastId } = action
+      const { toastId } = action;
 
       // ! Side effects ! - This could be extracted into a dismissToast() action,
       // but I'll keep it here for simplicity
       if (toastId) {
-        addToRemoveQueue(toastId)
+        addToRemoveQueue(toastId);
       } else {
         state.toasts.forEach((toast) => {
-          addToRemoveQueue(toast.id)
-        })
+          addToRemoveQueue(toast.id);
+        });
       }
 
       return {
@@ -111,46 +108,46 @@ export const reducer = (state: State, action: Action): State => {
                 ...t,
                 open: false,
               }
-            : t
+            : t,
         ),
-      }
+      };
     }
     case "REMOVE_TOAST":
       if (action.toastId === undefined) {
         return {
           ...state,
           toasts: [],
-        }
+        };
       }
       return {
         ...state,
         toasts: state.toasts.filter((t) => t.id !== action.toastId),
-      }
+      };
   }
-}
+};
 
-const listeners: Array<(state: State) => void> = []
+const listeners: Array<(state: State) => void> = [];
 
-let memoryState: State = { toasts: [] }
+let memoryState: State = { toasts: [] };
 
 function dispatch(action: Action) {
-  memoryState = reducer(memoryState, action)
+  memoryState = reducer(memoryState, action);
   listeners.forEach((listener) => {
-    listener(memoryState)
-  })
+    listener(memoryState);
+  });
 }
 
-type Toast = Omit<ToasterToast, "id">
+type Toast = Omit<ToasterToast, "id">;
 
 function toast({ ...props }: Toast) {
-  const id = genId()
+  const id = genId();
 
   const update = (props: ToasterToast) =>
     dispatch({
       type: "UPDATE_TOAST",
       toast: { ...props, id },
-    })
-  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id })
+    });
+  const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id });
 
   dispatch({
     type: "ADD_TOAST",
@@ -159,36 +156,36 @@ function toast({ ...props }: Toast) {
       id,
       open: true,
       onOpenChange: (open) => {
-        if (!open) dismiss()
+        if (!open) dismiss();
       },
     },
-  })
+  });
 
   return {
     id: id,
     dismiss,
     update,
-  }
+  };
 }
 
 function useToast() {
-  const [state, setState] = React.useState<State>(memoryState)
+  const [state, setState] = React.useState<State>(memoryState);
 
   React.useEffect(() => {
-    listeners.push(setState)
+    listeners.push(setState);
     return () => {
-      const index = listeners.indexOf(setState)
+      const index = listeners.indexOf(setState);
       if (index > -1) {
-        listeners.splice(index, 1)
+        listeners.splice(index, 1);
       }
-    }
-  }, [state])
+    };
+  }, [state]);
 
   return {
     ...state,
     toast,
     dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }),
-  }
+  };
 }
 
-export { useToast, toast }
+export { useToast, toast };
diff --git a/src/lib/supabase-server.ts b/src/lib/supabase-server.ts
new file mode 100644
index 0000000..fb59586
--- /dev/null
+++ b/src/lib/supabase-server.ts
@@ -0,0 +1,7 @@
+// lib/supabase-server.ts
+import { createServerComponentClient } from "@supabase/auth-helpers-nextjs";
+import { cookies } from "next/headers";
+
+export function createServerClient() {
+  return createServerComponentClient({ cookies });
+}


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🧹 Nitpick comments (17)</summary><blockquote>

<details>
<summary>src/components/modules/auth/ui/forms/LoginForm.tsx (1)</summary><blockquote>

`112-117`: **Ensure consistent JSX indentation for the new "Forgot your password?" link block.**  
The `<Link>` should be indented to reflect its nesting inside the `<div>`, consistent with nearby JSX elements for readability:

```diff
-          
-                <div className="text-center text-sm">
-                <Link href="/forgot-password" className="underline underline-offset-4">
-                  Forgot your password?
-                </Link>
-              </div>
+              <div className="text-center text-sm">
+                <Link href="/forgot-password" className="underline underline-offset-4">
+                  Forgot your password?
+                </Link>
+              </div>
```

</blockquote></details>
<details>
<summary>src/lib/supabase-server.ts (1)</summary><blockquote>

`1-7`: **New server‐side Supabase client helper looks correct.**

The `createServerClient` function properly wraps `createServerComponentClient` with Next.js `cookies` for server‐side authentication support. Consider adding an explicit return type (for example, `: SupabaseClient`) on `createServerClient` to improve type safety in TypeScript.

</blockquote></details>
<details>
<summary>src/app/(auth)/forgot-password/page.tsx (1)</summary><blockquote>

`9-19`: **Remove empty wrapper div or add page heading**  
The `<div className="flex flex-col space-y-2 text-center">` at lines 13–14 is empty. Consider removing it or populating it with a heading (e.g., an `<h1>` like “Reset Your Password”) for better semantic structure and user guidance.

</blockquote></details>
<details>
<summary>src/app/(auth)/reset-password/page.tsx (1)</summary><blockquote>

`9-12`: **Remove console.log statement**

The console.log statement should be removed from production code as it's likely a debugging statement that was left in.

```diff
export default function ResetPasswordPage() {
-    console.log("Page loaded");
    return <ResetPasswordClient />;
}
```

</blockquote></details>
<details>
<summary>src/components/modules/auth/ui/forms/ForgotPassword.tsx (3)</summary><blockquote>

`1-3`: **Remove unused import**

The useState import is defined but never used in this component.

```diff
"use client";

-import { useState } from "react";
import { cn } from "@/lib/utils";
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 3-3: 'useState' is defined but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

---

`62-63`: **Escape apostrophe in text content**

React requires escaping apostrophes in JSX text content.

```diff
                    <p className="text-sm text-muted-foreground">
-                      If an account exists with that email, we've sent a password reset link.
+                      If an account exists with that email, we&apos;ve sent a password reset link.
                      Please check your inbox and spam folder.
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 62-62: `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.

(react/no-unescaped-entities)

</details>

</details>

---

`82-84`: **Simplify redundant onChange handler**

The onChange handler is simply calling the field.onChange method without any additional logic, making it redundant.

```diff
                            <Input
                              placeholder="Enter email"
                              type="email"
                              {...field}
-                              onChange={(e) => {
-                                field.onChange(e);
-                              }}
                            />
```

</blockquote></details>
<details>
<summary>src/components/modules/auth/ui/pages/ResetPasswordClient.tsx (3)</summary><blockquote>

`15-15`: **Remove unused variable**

The `searchParams` variable is declared but never used.

```diff
-  const searchParams = useSearchParams();
  const router = useRouter();
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 15-15: 'searchParams' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

---

`49-49`: **Fix typo in user message**

There's a typo in the user message - "recharge" should be "reload".

```diff
-            Please don't recharge the page
+            Please don't reload the page
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 49-49: `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.

(react/no-unescaped-entities)

</details>

</details>

---

`59-68`: **Consider adding empty state messaging in the UI**

The component renders an empty div with no content for the header area, which could benefit from a title or instructional text.

```diff
      <div className="mx-auto flex w-full flex-col justify-center space-y-6 sm:w-[350px]">
        <div className="flex flex-col space-y-2 text-center">
+           <h1 className="text-xl font-semibold">Create New Password</h1>
+           <p className="text-sm text-muted-foreground">Your reset link is valid! Please enter a new password below.</p>
        </div>
        <ResetPasswordForm />
```

</blockquote></details>
<details>
<summary>src/components/modules/auth/ui/forms/ResetPasswordForm.tsx (3)</summary><blockquote>

`62-64`: **Unused state variables should be removed or utilized**

The state variables `isPasswordValid` and `passwordsMatch` are being set but never used in the component's rendering. If these were intended for UI feedback, consider adding visual indicators when passwords don't match or don't meet requirements.

```diff
-  // State to track if password meets minimum length
-  const [isPasswordValid, setIsPasswordValid] = useState(true);
-  // State to track if passwords match
-  const [passwordsMatch, setPasswordsMatch] = useState(true);
```

Alternatively, if you want to keep and use these states, add visual feedback:
```diff
  <FormField
    control={methods.control}
    name="password"
    render={({ field }) => (
      <FormItem>
        <FormLabel>New Password</FormLabel>
        <FormControl>
          <div className="relative">
            <Input
              type={showPassword ? "text" : "password"}
              {...field}
+             className={`pr-10 ${!isPasswordValid ? "border-red-500" : ""}`}
-             className="pr-10"
            />
            {/* Toggle button to show/hide password */}
            <button
              type="button"
              className="absolute right-2 top-1/2 -translate-y-1/2 text-muted-foreground"
              onClick={() => setShowPassword((prev) => !prev)}
            >
              {showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
            </button>
          </div>
        </FormControl>
+       {!isPasswordValid && field.value.length > 0 && (
+         <p className="text-sm text-red-500 mt-1">Password must be at least 8 characters</p>
+       )}
        <FormMessage />
      </FormItem>
    )}
  />
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 62-62: 'isPasswordValid' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

---

[error] 64-64: 'passwordsMatch' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

---

`135-135`: **Enhance error message handling for better user experience**

The current error display is generic. Consider categorizing common errors and providing more user-friendly messages for specific failure scenarios.

```diff
- {error && <p className="text-sm text-red-500 mb-4">{error}</p>}
+ {error && (
+   <div className="bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-md p-3 mb-4">
+     <p className="text-sm text-red-600 dark:text-red-400">
+       {error.includes("Invalid login credentials") 
+         ? "Your reset link has expired. Please request a new one." 
+         : error}
+     </p>
+   </div>
+ )}
```

---

`112-114`: **Consider using Next.js router for navigation instead of window.location**

Using `window.location.href` for navigation causes a full page reload. Since you're already using Next.js, consider using the router for a smoother experience.

```diff
- <Button onClick={() => (window.location.href = "/login")}>
+ <Button onClick={() => router.push("/login")}>
  Log in
</Button>
```

You'd need to import and use the router from your hook:
```javascript
const { router } = usePasswordReset();
```

</blockquote></details>
<details>
<summary>src/hooks/password-reset.hook.ts (4)</summary><blockquote>

`80-83`: **Replace `any` type with more specific error typing**

Using `any` type for error handling reduces TypeScript's ability to provide type safety. Consider using a more specific type for better error handling and type checking.

```diff
- } catch (err: any) {
+ } catch (err: unknown) {
+   const errorMessage = err instanceof Error ? err.message : "Failed to send reset email";
-   console.error("Password reset request error:", err.message);
-   setError(err.message || "Failed to send reset email");
+   console.error("Password reset request error:", errorMessage);
+   setError(errorMessage);
    toast.error("Failed to send reset email");
```

And similarly for the second error handler:

```diff
- } catch (err: any) {
+ } catch (err: unknown) {
+   const errorMessage = err instanceof Error ? err.message : "Failed to reset password";
-   setError(err.message || "Failed to reset password");
+   setError(errorMessage);
    toast.error("Failed to reset password");
```


Also applies to: 109-111

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 80-80: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

</details>

</details>

---

`103-103`: **Decide on toast notification usage**

There's a commented-out toast success notification. Either remove it completely or uncomment it if you want to show both the success UI and a toast notification.

```diff
- // toast.success("Password updated successfully");
+ toast.success("Password updated successfully");
```

Or remove it entirely if you prefer to only show the success UI without additional toast notifications.

---

`95-98`: **Add more documentation about token handling**

The current comment is helpful but could be expanded to better explain how Supabase handles the token and session for password resets.

```diff
- // Supabase handles the token internally via the current session
+ // Supabase handles the token internally via the current session
+ // When a user clicks the reset link in their email, Supabase automatically
+ // creates a session with the token in the URL, allowing us to call updateUser
+ // without explicitly passing the token
```

---

`70-74`: **Enhance rate limit error handling**

The current implementation handles rate limit errors, but could be improved by:
1. Setting isLoading to false before returning
2. Providing more context to the user about when they can try again


```diff
if (error) {
  if (error.message.includes("30 seconds")) {
-    toast.info("Please wait 30 seconds before trying again.");
+    toast.info("Rate limited: Please wait 30 seconds before trying again.");
+    setIsLoading(false);
    return;
  }
  throw error;
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 06d5d7a56b7ff2dcb23bee6a5078179542d33216 and a0b2d41afcdd13e093a0254a62084589509ca51f.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `package-lock.json` is excluded by `!**/package-lock.json`

</details>

<details>
<summary>📒 Files selected for processing (21)</summary>

* `README.md` (2 hunks)
* `package.json` (2 hunks)
* `src/app/(auth)/forgot-password/page.tsx` (1 hunks)
* `src/app/(auth)/reset-password/page.tsx` (1 hunks)
* `src/app/api/(auth)/forgot-password/route.ts` (1 hunks)
* `src/app/layout.tsx` (1 hunks)
* `src/components/layout/sidebar/app-sidebar.tsx` (2 hunks)
* `src/components/layout/sidebar/theme-toggler-switch.tsx` (2 hunks)
* `src/components/layout/sidebar/theme-toggler.tsx` (1 hunks)
* `src/components/modules/auth/ui/forms/ForgotPassword.tsx` (1 hunks)
* `src/components/modules/auth/ui/forms/LoginForm.tsx` (1 hunks)
* `src/components/modules/auth/ui/forms/ResetPasswordForm.tsx` (1 hunks)
* `src/components/modules/auth/ui/pages/ResetPasswordClient.tsx` (1 hunks)
* `src/components/ui/form.tsx` (7 hunks)
* `src/components/ui/switch.tsx` (2 hunks)
* `src/components/ui/toast.tsx` (9 hunks)
* `src/components/ui/toaster.tsx` (2 hunks)
* `src/hooks/password-reset.hook.ts` (1 hunks)
* `src/hooks/theme-provider.tsx` (1 hunks)
* `src/hooks/use-toast.ts` (3 hunks)
* `src/lib/supabase-server.ts` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (7)</summary>

<details>
<summary>src/hooks/use-toast.ts (1)</summary><blockquote>

<details>
<summary>src/components/ui/toast.tsx (3)</summary>

* `ToastProps` (120-120)
* `ToastActionElement` (121-121)
* `Toast` (124-124)

</details>

</blockquote></details>
<details>
<summary>src/components/ui/form.tsx (1)</summary><blockquote>

<details>
<summary>src/lib/utils.ts (1)</summary>

* `cn` (4-6)

</details>

</blockquote></details>
<details>
<summary>src/components/modules/auth/ui/pages/ResetPasswordClient.tsx (2)</summary><blockquote>

<details>
<summary>src/lib/supabase.ts (1)</summary>

* `supabase` (3-13)

</details>
<details>
<summary>src/components/modules/auth/ui/forms/ResetPasswordForm.tsx (1)</summary>

* `ResetPasswordForm` (48-209)

</details>

</blockquote></details>
<details>
<summary>src/components/modules/auth/ui/forms/ResetPasswordForm.tsx (5)</summary><blockquote>

<details>
<summary>src/hooks/password-reset.hook.ts (1)</summary>

* `usePasswordReset` (37-130)

</details>
<details>
<summary>src/components/ui/card.tsx (4)</summary>

* `Card` (77-77)
* `CardHeader` (78-78)
* `CardTitle` (80-80)
* `CardContent` (82-82)

</details>
<details>
<summary>src/components/ui/button.tsx (1)</summary>

* `Button` (57-57)

</details>
<details>
<summary>src/components/ui/form.tsx (5)</summary>

* `FormField` (178-178)
* `FormItem` (173-173)
* `FormLabel` (174-174)
* `FormControl` (175-175)
* `FormMessage` (177-177)

</details>
<details>
<summary>src/components/ui/input.tsx (1)</summary>

* `Input` (22-22)

</details>

</blockquote></details>
<details>
<summary>src/app/api/(auth)/forgot-password/route.ts (1)</summary><blockquote>

<details>
<summary>src/lib/supabase-server.ts (1)</summary>

* `createServerClient` (5-7)

</details>

</blockquote></details>
<details>
<summary>src/components/ui/switch.tsx (1)</summary><blockquote>

<details>
<summary>src/lib/utils.ts (1)</summary>

* `cn` (4-6)

</details>

</blockquote></details>
<details>
<summary>src/hooks/password-reset.hook.ts (2)</summary><blockquote>

<details>
<summary>src/lib/supabase.ts (1)</summary>

* `supabase` (3-13)

</details>
<details>
<summary>src/hooks/use-toast.ts (1)</summary>

* `toast` (191-191)

</details>

</blockquote></details>

</details><details>
<summary>🪛 ESLint</summary>

<details>
<summary>src/hooks/use-toast.ts</summary>

[error] 18-18: 'actionTypes' is assigned a value but only used as a type.

(@typescript-eslint/no-unused-vars)

</details>
<details>
<summary>src/components/modules/auth/ui/forms/ForgotPassword.tsx</summary>

[error] 3-3: 'useState' is defined but never used.

(@typescript-eslint/no-unused-vars)

---

[error] 62-62: `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.

(react/no-unescaped-entities)

</details>
<details>
<summary>src/components/modules/auth/ui/pages/ResetPasswordClient.tsx</summary>

[error] 15-15: 'searchParams' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

---

[error] 49-49: `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.

(react/no-unescaped-entities)

</details>
<details>
<summary>src/components/modules/auth/ui/forms/ResetPasswordForm.tsx</summary>

[error] 62-62: 'isPasswordValid' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

---

[error] 64-64: 'passwordsMatch' is assigned a value but never used.

(@typescript-eslint/no-unused-vars)

</details>
<details>
<summary>src/app/api/(auth)/forgot-password/route.ts</summary>

[error] 37-37: 'error' is defined but never used.

(@typescript-eslint/no-unused-vars)

</details>
<details>
<summary>src/hooks/password-reset.hook.ts</summary>

[error] 80-80: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

---

[error] 109-109: Unexpected any. Specify a different type.

(@typescript-eslint/no-explicit-any)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (24)</summary><blockquote>

<details>
<summary>src/components/layout/sidebar/theme-toggler-switch.tsx (1)</summary>

`10-10`: Also applies to: 26-26

</details>
<details>
<summary>package.json (1)</summary>

`42-42`: **Dependency addition looks good.**  
Adding `sonner@^2.0.3` aligns with the new toast notifications in the password reset flow. Ensure it’s imported and used in the relevant UI components (`Toast`, `Toaster`).

</details>
<details>
<summary>src/hooks/theme-provider.tsx (1)</summary>

`9-11`: Also applies to: 15-17, 20-22, 32-42, 44-60, 63-67

</details>
<details>
<summary>src/app/layout.tsx (1)</summary>

`33-33`: **Stylistic JSX formatting only – LGTM.**

The conversion of the `<ThemeProvider>` tag to a single line with the `defaultTheme="system"` prop and the removal of the extra blank line before the closing brace are purely stylistic changes that do not affect functionality.  



Also applies to: 40-40

</details>
<details>
<summary>src/components/ui/switch.tsx (2)</summary>

`1-7`: **Ensuring client‐side rendering context.**

The addition of the `"use client";` directive at the top and semicolons on the import statements correctly designate this component as a client‐only React component, which is required for hooks and Radix UI primitives to work properly.

---

`15-27`: **Consistent trailing commas and semicolons.**

The newly added commas in the `cn(...)` calls and the semicolons after the `React.forwardRef` invocation are purely formatting updates that improve code consistency and readability.

</details>
<details>
<summary>README.md (2)</summary>

`53-53`: **Markdown whitespace cleanup – LGTM.**

Removing the trailing space from the list item is purely a cosmetic change that tightens up the markdown formatting.

---

`72-72`: **Code‐fence indentation adjustment – LGTM.**

Adjusting the backtick indentation under the numbered list improves alignment and readability of the code block.

</details>
<details>
<summary>src/components/ui/toaster.tsx (4)</summary>

`1-3`: **Client component directive and hook import.**

Adding `"use client";` and importing `useToast` ensures that this toast provider component correctly runs in the client environment.

---

`11-12`: **Trailing comma in import list.**

The comma after `ToastViewport` is a non‐functional formatting tweak for consistency across import statements.

---

`14-14`: **Destructuring style is idiomatic.**

The `const { toasts } = useToast();` line remains unchanged in behavior and adheres to standard React hook usage.

---

`30-34`: **Closing tag indentation only.**

The adjustments to the closing `);` and related indentation are purely stylistic and do not impact logic or rendering.

</details>
<details>
<summary>src/hooks/use-toast.ts (1)</summary>

`1-17`: **Styling changes only**  
All other modifications (imports, semicolons, trailing commas, spacing) are purely stylistic. No functional or logical behavior was altered.  



Also applies to: 24-192

</details>
<details>
<summary>src/components/ui/form.tsx (1)</summary>

`1-180`: **Styling changes only**  
This file received only formatting updates—added semicolons, trailing commas, and consistent indentation. No functional, API, or behavior changes detected.

</details>
<details>
<summary>src/components/layout/sidebar/theme-toggler.tsx (1)</summary>

`1-41`: **Styling changes only**  
Whitespace and indentation were adjusted for consistency; the component’s logic and behavior remain unchanged.

</details>
<details>
<summary>src/components/ui/toast.tsx (1)</summary>

`1-130`: **Styling changes only**  
All updates (semicolons, trailing commas, minor JSX indentation tweaks) are purely stylistic. There are no changes to functionality or API.

</details>
<details>
<summary>src/app/(auth)/forgot-password/page.tsx (1)</summary>

`1-7`: **Metadata and imports look correct**  
The `metadata` object correctly sets the page title and description, and the `ForgotPasswordForm` import aligns with the new forgot-password feature.

</details>
<details>
<summary>src/app/api/(auth)/forgot-password/route.ts (2)</summary>

`25-28`: **Verify NEXT_PUBLIC_SITE_URL configuration**

The configuration looks correct, but it's important to ensure that `NEXT_PUBLIC_SITE_URL` is properly set in both development and production environments for the password reset functionality to work correctly.

Make sure this environment variable is configured in your deployment settings with the correct production URL. In development, it should point to localhost with the correct port.

---

`5-41`: **Implementation looks good overall**

The API route implements proper validation using Zod, uses appropriate status codes, and correctly integrates with Supabase's password reset functionality.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 37-37: 'error' is defined but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

</details>
<details>
<summary>src/app/(auth)/reset-password/page.tsx (1)</summary>

`1-12`: **Page setup looks good**

The page metadata is well-defined with appropriate title and description, and the component structure follows Next.js best practices.

</details>
<details>
<summary>src/components/modules/auth/ui/forms/ForgotPassword.tsx (2)</summary>

`118-121`: **Replace placeholder links with actual URLs**

The Terms of Service and Privacy Policy links currently use placeholder "#" URLs, which should be replaced with actual URLs before deploying to production.

Please verify that these links point to actual policy pages before deploying to production.

---

`20-124`: **Form implementation looks good overall**

The form is well-structured with clear validation, appropriate loading states, success messaging, and follows good UX patterns with clear labels and instructions.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 62-62: `'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`.

(react/no-unescaped-entities)

</details>

</details>

</details>
<details>
<summary>src/components/modules/auth/ui/pages/ResetPasswordClient.tsx (1)</summary>

`19-37`: **Authentication token validation looks good**

The session validation logic using Supabase's auth.getSession() is correctly implemented, with appropriate error handling and user redirection when the token is invalid.

</details>
<details>
<summary>src/hooks/password-reset.hook.ts (1)</summary>

`63-68`: <details>
<summary>✅ Verification successful</summary>

**LGTM but verify redirect configuration in Supabase dashboard**

The implementation of `redirectTo` for password reset emails is correct, but remember that for this to work in production, you'll need to add the production URL to the allowed redirect URLs in your Supabase project settings.


---

🌐 Web query:

```web
How to configure redirect URLs in Supabase authentication settings?
```

💡 Result:

## How to Configure Redirect URLs in Supabase Authentication Settings

**Overview**

Configuring redirect URLs in Supabase is essential to control where your users are sent after authentication events such as signup, login, magic link email confirmation, or third-party OAuth sign-in. Supabase uses an allow-list mechanism: only URLs on this list are permitted as redirect targets for security reasons[1][4][3].

---

**Steps to Configure Redirect URLs**

**1. Access the Supabase Dashboard**
- Navigate to your Supabase project dashboard.

**2. Go to Authentication Settings**
- Open the “Authentication” section.
- Locate the “URL Configuration” or “Redirect URIs”/“Redirect URLs” area. This is sometimes under a “Public integration” heading[4][9].

**3. Add Allowed Redirect URLs**
- In the “Redirect URLs” list, add every full URL you want to allow.
    - For local development, add URLs like `http://localhost:3000`.
    - For production, add your actual domain, e.g. `https://yourdomain.com/auth/callback`.
- You may use wildcards to match multiple subdomains or paths, e.g., `https://*.example.com/auth/callback`[1][4].
- Each URL must match exactly (unless a wildcard is used). Mismatches are the most common source of redirect issues[3][10].

**4. Save Your Changes**
- Click the “Save” button after making changes. This is required to apply the new allow list[9].

---

**Using the `redirectTo` Parameter**

When using Supabase client SDKs (for magic links, passwordless, or OAuth logins), you can specify a `redirectTo` parameter to control where users are sent after authentication. The specified URL must be on the allowed redirect URLs list[1][3].

Example:
```js
const { user, session, error } = await supabase.auth.signInWithOtp({
  email: 'user@example.com',
  options: {
    redirectTo: 'https://yourdomain.com/welcome'
  }
})
```
If you do not specify `redirectTo`, the default is the "Site URL" configured in your Supabase settings[1][8].

---

**Editing Email Templates for Redirects**

If you utilize the `redirectTo` parameter, update your Supabase email templates to use the `{{ .RedirectTo }}` variable instead of `{{ .SiteURL }}` to ensure confirmation and magic link emails lead users to the intended page[1].

Example change in email template:
```html
<!-- Old -->
<a href="{{ .SiteURL }}/auth/confirm?...">Confirm your email</a>
<!-- New -->
<a href="{{ .RedirectTo }}/auth/confirm?...">Confirm your email</a>
```

---

**Supporting Mobile and Deep Linking**

For mobile apps, you can use custom deep link URIs such as `com.yourapp://login-callback/` in both your "Site URL" and allowed redirect URLs[1].

---

**Troubleshooting Tips**

- Ensure the `redirectTo` URL exactly matches an entry in your Supabase Redirect URLs list (consider wildcards if you use multiple environments)[3][10].
- If redirects go to an unexpected place (e.g., always to localhost or the default), check for typos and that the URL is saved correctly in your settings[3][9][10].
- For third-party OAuth providers, you must also configure their callback URLs to match your Supabase settings[7].

---

**Summary Table: Key Steps**

| Step | Action                                                      | Where                                  |
|------|-------------------------------------------------------------|----------------------------------------|
| 1    | Open Supabase dashboard                                     | Supabase web UI                        |
| 2    | Go to Authentication → URL Configuration                    | Project settings                       |
| 3    | Add allowed redirect URLs (with optional wildcards)         | “Redirect URLs” or “Public integration”|
| 4    | Save your changes                                           | Bottom of the page                     |
| 5    | Use the `redirectTo` parameter in your client auth methods  | Your application code                  |
| 6    | Update email templates to use `{{ .RedirectTo }}` if needed | Email template editor                  |

---

**References for More Detail**
- [Supabase Auth Redirect URLs Documentation][1]
- [Supabase URL Troubleshooting][3]
- [Restack Supabase Redirect Guide][4]
- [Lovable Blog Step-by-Step Guide][9]

[1][3][4][9]

Citations:

- 1: https://supabase.com/docs/guides/auth/redirect-urls
- 2: https://github.com/orgs/supabase/discussions/6088
- 3: https://supabase.com/docs/guides/troubleshooting/why-am-i-being-redirected-to-the-wrong-url-when-using-auth-redirectto-option-_vqIeO
- 4: https://www.restack.io/docs/supabase-knowledge-supabase-redirect-url-guide
- 5: https://supabase.com/docs/guides/auth/general-configuration
- 6: https://supabase.com/docs/guides/auth
- 7: https://community.weweb.io/t/tutorial-for-setting-up-supabase-login-providers/3120
- 8: https://www.youtube.com/watch?v=Qaxt5-jEPrQ
- 9: https://lovable.dev/blog/supabase-authentification-step-by-step
- 10: https://github.com/orgs/supabase/discussions/27991

---



</details>

**Ensure Supabase Redirect URLs Are Configured**  
- In your Supabase Dashboard, go to Authentication → URL Configuration → Redirect URLs.  
- Add each full URL that will receive the reset link, for example:  
  • http://localhost:3000/reset-password  
  • https://yourdomain.com/reset-password  
- Save your changes to apply the new allow-list.  
- (If you’ve customized your email templates, switch from `{{ .SiteURL }}` to `{{ .RedirectTo }}` so the reset link points to the correct path.)  

LGTM — no code changes required.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T00:47:11Z

Review comments:
diff_hunk: @@ -30,6 +30,7 @@
     "class-variance-authority": "^0.7.1",
     "clsx": "^2.1.1",
     "framer-motion": "^12.9.2",
+    "gig-fox": "file:",
path: package.json
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Invalid file path specifier for "gig-fox".**  
The `"file:"` dependency must include a valid path (e.g., `"file:../path-to-package"`). As written, npm will not resolve it and installation will fail.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:47:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,106 +1,103 @@
-"use client"
+"use client";
 
 // Inspired by react-hot-toast library
-import * as React from "react"
+import * as React from "react";
 
-import type {
-  ToastActionElement,
-  ToastProps,
-} from "@/components/ui/toast"
+import type { ToastActionElement, ToastProps } from "@/components/ui/toast";
 
-const TOAST_LIMIT = 1
-const TOAST_REMOVE_DELAY = 1000000
+const TOAST_LIMIT = 1;
+const TOAST_REMOVE_DELAY = 1000000;
 
 type ToasterToast = ToastProps & {
-  id: string
-  title?: React.ReactNode
-  description?: React.ReactNode
-  action?: ToastActionElement
-}
+  id: string;
+  title?: React.ReactNode;
+  description?: React.ReactNode;
+  action?: ToastActionElement;
+};
 
 const actionTypes = {
   ADD_TOAST: "ADD_TOAST",
   UPDATE_TOAST: "UPDATE_TOAST",
   DISMISS_TOAST: "DISMISS_TOAST",
   REMOVE_TOAST: "REMOVE_TOAST",
-} as const
+} as const;
path: src/hooks/use-toast.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Remove or utilize `actionTypes` constant to resolve ESLint warning**  
The `actionTypes` constant is only used for typing and never referenced at runtime, triggering the ESLint error `'actionTypes' is assigned a value but only used as a type`. To fix this, either:  
- Reference the constants in your dispatch calls (e.g., `type: actionTypes.ADD_TOAST`) to avoid string literals, or  
- Convert `actionTypes` into a purely type-level declaration (e.g., `type ActionTypes = { ... }`) and remove the unused value binding.

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 18-18: 'actionTypes' is assigned a value but only used as a type.

(@typescript-eslint/no-unused-vars)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:47:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+import { NextResponse } from "next/server";
+import { z } from "zod";
+import { createServerClient } from "@/lib/supabase-server";
+
+// Define schema for validating email input
+const ForgotPasswordSchema = z.object({
+  email: z.string().email(),
+});
+
+export async function POST(req: Request) {
+  const body = await req.json();
+  const parsed = ForgotPasswordSchema.safeParse(body);
+
+  // If the email is invalid, return 400
+  if (!parsed.success) {
+    return NextResponse.json({ error: "Invalid email address" }, { status: 400 });
+  }
+
+  const { email } = parsed.data;
+
+  try {
+    // Create a Supabase server client
+    const supabase = createServerClient();
+
+    // Send password reset email with redirect URL
+    const { error } = await supabase.auth.resetPasswordForEmail(email, {
+      redirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/reset-password`,
+    });
+
+    if (error) {
+      // Return any Supabase-related error
+      return NextResponse.json({ error: error.message }, { status: 500 });
+    }
+
+    // Return success response
+    return NextResponse.json({ message: "Password reset email sent" }, { status: 200 });
+  } catch (error) {
+    // Catch unexpected errors
+    return NextResponse.json({ error: "Server error" }, { status: 500 });
+  }
path: src/app/api/(auth)/forgot-password/route.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Error handling improvement needed in catch block**

The error variable in the catch block is defined but never used, resulting in a generic error message regardless of the specific error that occurred.

This could be improved by:

```diff
  } catch (error) {
    // Catch unexpected errors
-   return NextResponse.json({ error: "Server error" }, { status: 500 });
+   console.error("Forgot password error:", error);
+   return NextResponse.json({ 
+     error: error instanceof Error ? error.message : "Server error" 
+   }, { status: 500 });
  }
```


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
  } catch (error) {
    // Catch unexpected errors
    console.error("Forgot password error:", error);
    return NextResponse.json({
      error: error instanceof Error ? error.message : "Server error"
    }, { status: 500 });
  }
`````

</details>

<!-- suggestion_end -->

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 ESLint</summary>

[error] 37-37: 'error' is defined but never used.

(@typescript-eslint/no-unused-vars)

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:47:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,130 @@
+"use client";
+
+import { useState } from "react";
+import { useForm } from "react-hook-form";
+import { zodResolver } from "@hookform/resolvers/zod";
+import { z } from "zod";
+import { supabase } from "@/lib/supabase";
+import { toast } from "sonner";
+import { useRouter } from "next/navigation";
+
+// Email validation schema
+const forgotPasswordSchema = z.object({
+  email: z.string().min(1, "Email is required").email("Please enter a valid email address"),
+});
+
+type ForgotPasswordFormValues = z.infer<typeof forgotPasswordSchema>;
+
+// Password reset validation schema
+const resetPasswordSchema = z
+  .object({
+    password: z
+      .string()
+      .min(8, "Password must be at least 8 characters")
+      .regex(
+        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
+        "Password must contain an uppercase letter, a lowercase letter, a number, and a special character"
+      ),
path: src/hooks/password-reset.hook.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Different password regex patterns between hook and form component**

I noticed that the password regex pattern in this hook is different from the one used in the ResetPasswordForm component, which implements multiple separate regex checks. Consider standardizing the approach for consistency.

```diff
-    password: z
-      .string()
-      .min(8, "Password must be at least 8 characters")
-      .regex(
-        /^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/,
-        "Password must contain an uppercase letter, a lowercase letter, a number, and a special character"
-      ),
+    password: z
+      .string()
+      .min(8, "Password must be at least 8 characters")
+      .regex(/[A-Z]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[a-z]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[0-9]/, "Password must include uppercase, lowercase, number and special character")
+      .regex(/[^A-Za-z0-9]/, "Password must include uppercase, lowercase, number and special character"),
```

Alternatively, update the ResetPasswordForm to use the same regex pattern as defined here.


<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
      password: z
        .string()
        .min(8, "Password must be at least 8 characters")
        .regex(/[A-Z]/, "Password must include uppercase, lowercase, number and special character")
        .regex(/[a-z]/, "Password must include uppercase, lowercase, number and special character")
        .regex(/[0-9]/, "Password must include uppercase, lowercase, number and special character")
        .regex(/[^A-Za-z0-9]/, "Password must include uppercase, lowercase, number and special character"),
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-02T00:47:10Z
author_association: CONTRIBUTOR

PR comments:
user: login: coderabbitai[bot]
created_at: 2025-05-02T00:44:43Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This update implements a full-stack "Forgot Password" flow using Supabase authentication. It introduces new API endpoints, React components, and hooks for handling password reset requests and submissions. The login page now includes a "Forgot your password?" link. Users can request a password reset link via email, receive a Supabase-generated email, and follow a secure link to a reset page. The reset page validates the token, allows entry of a new password with strength validation, and provides user feedback for success and error states. Additional changes include code style improvements, dependency updates, and UI responsiveness using ShadCN components.

## Changes

| Files/Groups                                                                                       | Change Summary                                                                                                                                                                                                                                                                                                                                                                  |
|----------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `src/app/(auth)/forgot-password/page.tsx`, `src/app/(auth)/reset-password/page.tsx`                | Added new React page components for forgot password and reset password flows, including page metadata and integration with respective form components.                                                                                                                                            |
| `src/components/modules/auth/ui/forms/ForgotPassword.tsx`, `src/components/modules/auth/ui/forms/ResetPasswordForm.tsx` | Introduced new form components for requesting a password reset link and for submitting a new password, with validation, loading states, success/error messages, and UI feedback.                                                                                                                 |
| `src/components/modules/auth/ui/pages/ResetPasswordClient.tsx`                                     | Added a client component to validate the reset token via Supabase, handle redirects, and render the password reset form upon successful validation.                                                                                                                                                                                     |
| `src/app/api/(auth)/forgot-password/route.ts`                                                      | Implemented a POST API route for handling forgot password requests, validating input, and sending reset emails via Supabase.                                                                                                                                                                                                            |
| `src/hooks/password-reset.hook.ts`                                                                 | Introduced a custom React hook to manage password reset flows, including form state, Supabase API integration, validation, error handling, and user feedback.                                                                                                                                    |
| `src/lib/supabase-server.ts`                                                                       | Added a utility for creating a Supabase server client with cookie-based authentication.                                                                                                                                                                                                                                                 |
| `src/components/modules/auth/ui/forms/LoginForm.tsx`                                               | Added a "Forgot your password?" link to the login form, linking to the forgot password page.                                                                                                                                                                                                     |
| `package.json`                                                                                     | Added new dependencies: `gig-fox` and `sonner`.                                                                                                                                                                                                                                                  |
| `src/components/ui/form.tsx`, `src/components/ui/switch.tsx`, `src/components/ui/toast.tsx`, `src/components/ui/toaster.tsx`, `src/hooks/use-toast.ts` | Code style updates: added semicolons, trailing commas, and consistent formatting. No logic changes.                                                                                                                                                                                              |
| `src/components/layout/sidebar/app-sidebar.tsx`, `src/components/layout/sidebar/theme-toggler-switch.tsx`, `src/components/layout/sidebar/theme-toggler.tsx`, `src/hooks/theme-provider.tsx`, `src/app/layout.tsx` | Whitespace, indentation, and formatting adjustments only. No functional changes.                                                                                                                                                                          |
| `README.md`                                                                                        | Formatting and whitespace corrections. No content changes.                                                                                                                                                                                                                                       |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant User
    participant LoginPage
    participant ForgotPasswordPage
    participant API as ForgotPassword API
    participant Supabase
    participant Email
    participant ResetPasswordPage
    participant ResetPasswordClient
    participant SupabaseSession

    User->>LoginPage: Clicks "Forgot your password?"
    LoginPage->>ForgotPasswordPage: Navigates to forgot-password page
    User->>ForgotPasswordPage: Submits email for reset
    ForgotPasswordPage->>API: POST forgot-password request
    API->>Supabase: resetPasswordForEmail(email, redirect_url)
    Supabase-->>API: Success/Error
    API-->>ForgotPasswordPage: Success/Error response
    ForgotPasswordPage-->>User: Shows success or error message
    Supabase-->>Email: Sends reset link to user
    User->>Email: Clicks reset link
    Email->>ResetPasswordPage: Navigates to reset-password page with token
    ResetPasswordPage->>ResetPasswordClient: Loads client, validates token
    ResetPasswordClient->>SupabaseSession: Checks session/token validity
    SupabaseSession-->>ResetPasswordClient: Valid/Invalid
    alt Valid
        ResetPasswordClient->>User: Shows reset password form
        User->>ResetPasswordClient: Submits new password
        ResetPasswordClient->>Supabase: updateUser(new password)
        Supabase-->>ResetPasswordClient: Success/Error
        ResetPasswordClient-->>User: Shows success or error message
    else Invalid
        ResetPasswordClient->>ForgotPasswordPage: Redirects with error
    end
```

## Assessment against linked issues

| Objective                                                                                                        | Addressed | Explanation                                                                                           |
|------------------------------------------------------------------------------------------------------------------|-----------|-------------------------------------------------------------------------------------------------------|
| Add "Forgot Password?" link to login page (#11)                                                                  | ✅        |                                                                                                       |
| Create forgot password form, email input, send reset via Supabase, handle errors/success (#11)                   | ✅        |                                                                                                       |
| Implement reset password page, validate token, password strength, confirmation, submit to Supabase (#11)         | ✅        |                                                                                                       |
| Provide user feedback: loading, error, and success messages; handle invalid/expired links (#11)                  | ✅        |                                                                                                       |
| Use ShadCN UI, ensure dark/light mode, responsive design for all new flows (#11)                                 | ✅        |                                                                                                       |

## Suggested labels

`enhancement`, `Core Dev`

## Poem

> A password lost, but hope’s not gone,  
> Supabase sends a magic dawn.  
> With forms that shimmer, light or night,  
> Reset your secrets, set things right.  
> A bunny hops with code so neat—  
> Your login woes, now obsolete!  
> 🥕✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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A=== -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details open="true">
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=GrantChain/GrantFox&utm_content=17):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: JoelVR17
created_at: 2025-05-02T00:48:12Z
body: @gchyuli good job!!

Thanks a lot for your work 🔥
author_association: CONTRIBUTOR