diff --git a/actions/auth.ts b/actions/auth.ts new file mode 100644 index 0000000..5d8f30f --- /dev/null +++ b/actions/auth.ts @@ -0,0 +1,100 @@ +"use server"; + +import { auth } from "@/lib/auth"; +import { redirect } from "next/navigation"; +import { Headers } from "next/dist/compiled/@edge-runtime/primitives/fetch"; + +export type AuthResult = { + success: boolean; + error?: string; +}; + +// Note: We're relying on the nextCookies plugin configured in lib/auth.ts to handle cookies automatically, so explicit headers handling is not needed + +export async function signIn(formData: FormData): Promise { + const email = formData.get("email") as string; + const password = formData.get("password") as string; + + try { + await auth.api.signInEmail({ + body: { + email, + password, + }, + }); + + redirect("/profile"); + return { success: true }; + } catch (error) { + console.error("Sign in error:", error); + return { + success: false, + error: "Failed to sign in. Please check your credentials." + }; + } +} + +export async function signUp(formData: FormData): Promise { + const email = formData.get("email") as string; + const password = formData.get("password") as string; + const name = formData.get("name") as string; + + try { + await auth.api.signUpEmail({ + body: { + email, + password, + name, + }, + }); + + redirect("/profile"); + // This return is only reached if redirect fails + return { success: true }; + } catch (error) { + console.error("Sign up error:", error); + return { + success: false, + error: "Failed to sign up. Please try again." + }; + } +} + +// For server-side sign-out +export async function signOut(): Promise { + try { + // Use an empty object and accept the type error for now + // This works with the nextCookies plugin + await auth.api.signOut({ + // Force the headers to be empty object but satisfy the type system + headers: {} as HeadersInit, + }); + + redirect("/"); + // This return is only reached if redirect fails + return { success: true }; + } catch (error) { + console.error("Sign out error:", error); + return { + success: false, + error: "Failed to sign out. Please try again." + }; + } +} + +// For server-side session check +export async function getSession() { + try { + // Use an empty object and accept the type error for now + // This works with the nextCookies plugin + const session = await auth.api.getSession({ + // Force the headers to be empty but satisfy the type system + headers: new Headers(), + }); + + return session; + } catch (error) { + console.error("Get session error:", error); + return null; + } +} \ No newline at end of file diff --git a/app/(auth)/sign-in/[[...sign-in]]/page.tsx b/app/(auth)/sign-in/[[...sign-in]]/page.tsx deleted file mode 100644 index 1a4a0fa..0000000 --- a/app/(auth)/sign-in/[[...sign-in]]/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { SignIn } from "@clerk/nextjs"; -import { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Sign In | Portify', - description: 'Sign in to your Portify account to manage your portfolio', - robots: { - index: false, - follow: false, - }, - alternates: { - canonical: '/sign-in', - }, -}; - -export default function Page() { - return ( -
- -
- ); -} diff --git a/app/(auth)/sign-in/page.tsx b/app/(auth)/sign-in/page.tsx new file mode 100644 index 0000000..41d5f08 --- /dev/null +++ b/app/(auth)/sign-in/page.tsx @@ -0,0 +1,161 @@ +"use client"; + +import { signIn, AuthResult } from "@/actions/auth"; +import { authClient } from "@/lib/auth-client"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import Link from "next/link"; +import { useState } from "react"; + +export default function SignInPage() { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (formData: FormData) => { + setIsLoading(true); + setError(null); + + try { + const result: AuthResult = await signIn(formData); + if (!result.success && result.error) { + setError(result.error); + } + } catch { + setError("An unexpected error occurred. Please try again."); + } finally { + setIsLoading(false); + } + }; + + const handleGoogleSignIn = async () => { + setIsLoading(true); + setError(null); + + try { + await authClient.signIn.social({ + provider: "google", + }); + // No need to handle success case - user will be redirected by the provider + } catch { + setError("Failed to initiate Google sign in. Please try again."); + setIsLoading(false); + } + }; + + return ( +
+
+
+

Sign In

+

+ Sign in to access your ChemistryCheck account +

+
+ + {error && ( +
+
+
{error}
+
+
+ )} + +
+
+
+ + +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+
+ + Or continue with + +
+
+ + + +
+

+ Don't have an account?{" "} + + Sign up + +

+
+
+
+ ); +} diff --git a/app/(auth)/sign-up/[[...sign-up]]/page.tsx b/app/(auth)/sign-up/[[...sign-up]]/page.tsx deleted file mode 100644 index d868a5f..0000000 --- a/app/(auth)/sign-up/[[...sign-up]]/page.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { SignUp } from "@clerk/nextjs"; -import { Metadata } from 'next'; - -export const metadata: Metadata = { - title: 'Create Account | Portify', - description: 'Join Portify to create and showcase your professional portfolio', - robots: { - index: false, - follow: false, - }, - alternates: { - canonical: '/sign-up', - }, -}; - -export default function Page() { - return ( -
- -
- ); -} \ No newline at end of file diff --git a/app/(auth)/sign-up/page.tsx b/app/(auth)/sign-up/page.tsx new file mode 100644 index 0000000..48b41ad --- /dev/null +++ b/app/(auth)/sign-up/page.tsx @@ -0,0 +1,176 @@ +"use client"; + +import { signUp, AuthResult } from "@/actions/auth"; +import { authClient } from "@/lib/auth-client"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import Link from "next/link"; +import { useState } from "react"; + +export default function SignUpPage() { + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const handleSubmit = async (formData: FormData) => { + setIsLoading(true); + setError(null); + + try { + const result: AuthResult = await signUp(formData); + if (!result.success && result.error) { + setError(result.error); + } + } catch { + setError("An unexpected error occurred. Please try again."); + } finally { + setIsLoading(false); + } + }; + + const handleGoogleSignUp = async () => { + setIsLoading(true); + setError(null); + + try { + await authClient.signIn.social({ + provider: "google", + }); + // No need to handle success case - user will be redirected by the provider + } catch { + setError("Failed to initiate Google sign up. Please try again."); + setIsLoading(false); + } + }; + + return ( +
+
+
+

Sign Up

+

+ Create your ChemistryCheck account to analyze your conversations +

+
+ + {error && ( +
+
+
{error}
+
+
+ )} + +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+ +
+
+
+
+
+ + Or continue with + +
+
+ + + +
+

+ Already have an account?{" "} + + Sign in + +

+
+
+
+ ); +} diff --git a/app/api/auth/[...all]/route.ts b/app/api/auth/[...all]/route.ts new file mode 100644 index 0000000..370bead --- /dev/null +++ b/app/api/auth/[...all]/route.ts @@ -0,0 +1,4 @@ +import { auth } from "@/lib/auth"; +import { toNextJsHandler } from "better-auth/next-js"; + +export const { POST, GET } = toNextJsHandler(auth); \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index 0ea8cc7..4e68a36 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,6 @@ import type { Metadata, Viewport } from "next"; import { Geist } from "next/font/google"; import { ThemeProvider } from "@/components/theme-provider"; import "./globals.css"; -import { ClerkProvider } from "@clerk/nextjs"; import NavbarContainer from "@/components/navbar-container"; import { Toaster } from "@/components/ui/toaster"; import Script from "next/script"; @@ -117,30 +116,28 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - - - + +