From 4260d4be08817393af96e7843ed828183137119f Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:48:14 +0200 Subject: feat: implement unified AuthForm component for login and signup pages --- apps/web/src/components/auth-form.tsx | 398 ++++++++++++++++++++++++++++++++++ 1 file changed, 398 insertions(+) create mode 100644 apps/web/src/components/auth-form.tsx (limited to 'apps/web/src/components/auth-form.tsx') diff --git a/apps/web/src/components/auth-form.tsx b/apps/web/src/components/auth-form.tsx new file mode 100644 index 00000000..182137ef --- /dev/null +++ b/apps/web/src/components/auth-form.tsx @@ -0,0 +1,398 @@ +"use client"; + +import { useRouter } from "next/navigation"; +import { signUp, signIn } from "@/lib/auth-client"; +import { Button } from "@/components/ui/button"; +import { + Card, + CardContent, + CardDescription, + CardHeader, + CardTitle, +} from "@/components/ui/card"; +import { useState } from "react"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import Link from "next/link"; +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; +import { ArrowLeft, Loader2 } from "lucide-react"; +import { GoogleIcon } from "@/components/icons"; +import { useForm } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; + +// Zod schemas +const loginSchema = z.object({ + email: z.string().email("Please enter a valid email address"), + password: z.string().min(1, "Password is required"), +}); + +const signupSchema = z.object({ + name: z.string().min(2, "Name must be at least 2 characters"), + email: z.string().email("Please enter a valid email address"), + password: z.string().min(8, "Password must be at least 8 characters"), +}); + +type LoginFormData = z.infer; +type SignupFormData = z.infer; + +interface AuthFormProps { + mode: "login" | "signup"; +} + +const authConfig = { + login: { + title: "Welcome back", + description: "Sign in to your account to continue", + buttonText: "Sign in", + linkText: "Don't have an account?", + linkHref: "/auth/signup", + linkLabel: "Sign up", + successRedirect: "/editor", + }, + signup: { + title: "Create your account", + description: "Get started with your free account today", + buttonText: "Create account", + linkText: "Already have an account?", + linkHref: "/auth/login", + linkLabel: "Sign in", + successRedirect: "/auth/login", + }, +} as const; + +interface AuthFormContentProps { + error: string | null; + setError: (error: string | null) => void; + isGoogleLoading: boolean; + config: typeof authConfig.login | typeof authConfig.signup; + router: ReturnType; +} + +function LoginFormContent({ + error, + setError, + isGoogleLoading, + config, + router, +}: AuthFormContentProps) { + const form = useForm({ + resolver: zodResolver(loginSchema), + defaultValues: { email: "", password: "" }, + }); + + const { isSubmitting } = form.formState; + const isAnyLoading = isSubmitting || isGoogleLoading; + + const onSubmit = async (data: LoginFormData) => { + setError(null); + + try { + const { error } = await signIn.email({ + email: data.email, + password: data.password, + }); + + if (error) { + setError(error.message || "An unexpected error occurred."); + return; + } + + router.push(config.successRedirect); + } catch (error) { + setError("An unexpected error occurred. Please try again."); + } + }; + + return ( +
+ + ( + + Email + + + + + + )} + /> + + ( + + Password + + + + + + )} + /> + + + + + ); +} + +function SignupFormContent({ + error, + setError, + isGoogleLoading, + config, + router, +}: AuthFormContentProps) { + const form = useForm({ + resolver: zodResolver(signupSchema), + defaultValues: { email: "", password: "", name: "" }, + }); + + const { isSubmitting } = form.formState; + const isAnyLoading = isSubmitting || isGoogleLoading; + + const onSubmit = async (data: SignupFormData) => { + setError(null); + + try { + const { error } = await signUp.email({ + name: data.name, + email: data.email, + password: data.password, + }); + + if (error) { + setError(error.message || "An unexpected error occurred."); + return; + } + + router.push(config.successRedirect); + } catch (error) { + setError("An unexpected error occurred. Please try again."); + } + }; + + return ( +
+ + ( + + Full Name + + + + + + )} + /> + + ( + + Email + + + + + + )} + /> + + ( + + Password + + + + + + )} + /> + + + + + ); +} + +export function AuthForm({ mode }: AuthFormProps) { + const router = useRouter(); + const [error, setError] = useState(null); + const [isGoogleLoading, setIsGoogleLoading] = useState(false); + const config = authConfig[mode]; + + const handleGoogleAuth = async () => { + setError(null); + setIsGoogleLoading(true); + + try { + await signIn.social({ + provider: "google", + }); + + router.push(config.successRedirect); + } catch (error) { + setError( + `Failed to ${mode === "login" ? "sign in" : "sign up"} with Google. Please try again.` + ); + setIsGoogleLoading(false); + } + }; + + return ( +
+ + + + + + {config.title} + + + {config.description} + + + +
+ {error && ( + + Error + {error} + + )} + + + +
+
+ +
+
+ + Or continue with + +
+
+ + {mode === "login" ? ( + + ) : ( + + )} +
+ +
+ {config.linkText}{" "} + + {config.linkLabel} + +
+
+
+
+ ); +} -- cgit v1.3.1 From d25e1eaa1d83617cecba822ac0fb24fe61bb3a08 Mon Sep 17 00:00:00 2001 From: Hyteq Date: Tue, 24 Jun 2025 11:29:15 +0300 Subject: feat: migrated packages properly, using packages in web app --- apps/web/package.json | 2 ++ apps/web/src/app/api/auth/[...all]/route.ts | 2 +- apps/web/src/components/auth-form.tsx | 2 +- apps/web/src/components/header.tsx | 2 +- apps/web/src/lib/auth-client.ts | 4 ---- apps/web/src/lib/auth.ts | 29 ---------------------------- bun.lockb | Bin 200656 -> 201960 bytes 7 files changed, 5 insertions(+), 36 deletions(-) delete mode 100644 apps/web/src/lib/auth-client.ts delete mode 100644 apps/web/src/lib/auth.ts (limited to 'apps/web/src/components/auth-form.tsx') diff --git a/apps/web/package.json b/apps/web/package.json index b66a8eeb..ed08734e 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -19,6 +19,8 @@ "@ffmpeg/util": "^0.12.2", "@hello-pangea/dnd": "^18.0.1", "@hookform/resolvers": "^3.9.1", + "@opencut/auth": "workspace:*", + "@opencut/db": "workspace:*", "@types/pg": "^8.15.4", "@upstash/ratelimit": "^2.0.5", "@upstash/redis": "^1.35.0", diff --git a/apps/web/src/app/api/auth/[...all]/route.ts b/apps/web/src/app/api/auth/[...all]/route.ts index 1fb57443..393fb7e7 100644 --- a/apps/web/src/app/api/auth/[...all]/route.ts +++ b/apps/web/src/app/api/auth/[...all]/route.ts @@ -1,4 +1,4 @@ -import { auth } from "@/lib/auth"; +import { auth } from "@opencut/auth/server"; import { toNextJsHandler } from "better-auth/next-js"; export const { POST, GET } = toNextJsHandler(auth); \ No newline at end of file diff --git a/apps/web/src/components/auth-form.tsx b/apps/web/src/components/auth-form.tsx index 3dd56036..72bddd3d 100644 --- a/apps/web/src/components/auth-form.tsx +++ b/apps/web/src/components/auth-form.tsx @@ -1,7 +1,7 @@ "use client"; import { useRouter } from "next/navigation"; -import { signUp, signIn } from "@/lib/auth-client"; +import { signUp, signIn } from "@opencut/auth/client"; import { Button } from "@/components/ui/button"; import { Card, diff --git a/apps/web/src/components/header.tsx b/apps/web/src/components/header.tsx index 9f842b24..d1e0518f 100644 --- a/apps/web/src/components/header.tsx +++ b/apps/web/src/components/header.tsx @@ -5,7 +5,7 @@ import Image from "next/image"; import { Button } from "./ui/button"; import { ArrowRight } from "lucide-react"; import { HeaderBase } from "./header-base"; -import { useSession } from "@/lib/auth-client"; +import { useSession } from "@opencut/auth/client"; import { getStars } from "@/lib/fetchGhStars"; import { Star } from "lucide-react"; import { useEffect, useState } from "react"; diff --git a/apps/web/src/lib/auth-client.ts b/apps/web/src/lib/auth-client.ts deleted file mode 100644 index 1ab4e636..00000000 --- a/apps/web/src/lib/auth-client.ts +++ /dev/null @@ -1,4 +0,0 @@ -import { createAuthClient } from "better-auth/react"; -export const { signIn, signUp, useSession } = createAuthClient({ - baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL!, -}); diff --git a/apps/web/src/lib/auth.ts b/apps/web/src/lib/auth.ts deleted file mode 100644 index d0175c97..00000000 --- a/apps/web/src/lib/auth.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { betterAuth } from "better-auth"; -import { drizzleAdapter } from "better-auth/adapters/drizzle"; -import { db } from "./db"; - -export const auth = betterAuth({ - database: drizzleAdapter(db, { - provider: "pg", - usePlural: true, - }), - secret: process.env.BETTER_AUTH_SECRET!, - user: { - deleteUser: { - enabled: true, - }, - }, - emailAndPassword: { - enabled: true, - }, - socialProviders: { - google: { - clientId: process.env.GOOGLE_CLIENT_ID as string, - clientSecret: process.env.GOOGLE_CLIENT_SECRET as string, - }, - }, - appName: "OpenCut", - trustedOrigins: ["http://localhost:3000"], -}); - -export type Auth = typeof auth; diff --git a/bun.lockb b/bun.lockb index 2f51e8f2..2871b5e6 100644 Binary files a/bun.lockb and b/bun.lockb differ -- cgit v1.3.1 From b9e1dcf23e29e0adb08119b92f91e81db23ebb26 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 10:32:19 +0200 Subject: refactor: change button variant from 'ghost' to 'text' across multiple components for consistency --- apps/web/src/app/(auth)/login/page.tsx | 2 +- apps/web/src/app/(auth)/signup/page.tsx | 2 +- apps/web/src/components/auth-form.tsx | 2 +- apps/web/src/components/editor/preview-panel.tsx | 2 +- apps/web/src/components/editor/timeline.tsx | 18 +++++++++--------- apps/web/src/components/header.tsx | 2 +- apps/web/src/components/ui/button.tsx | 2 +- apps/web/src/components/ui/calendar.tsx | 2 +- apps/web/src/components/ui/input.tsx | 2 +- apps/web/src/components/ui/pagination.tsx | 2 +- apps/web/src/components/ui/sidebar.tsx | 2 +- 11 files changed, 19 insertions(+), 19 deletions(-) (limited to 'apps/web/src/components/auth-form.tsx') diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx index 5f37ced9..9935e153 100644 --- a/apps/web/src/app/(auth)/login/page.tsx +++ b/apps/web/src/app/(auth)/login/page.tsx @@ -137,7 +137,7 @@ export default function LoginPage() { return (
@@ -558,7 +558,7 @@ export function Timeline() { - @@ -567,7 +567,7 @@ export function Timeline() { - @@ -576,7 +576,7 @@ export function Timeline() { - @@ -585,7 +585,7 @@ export function Timeline() { - @@ -594,7 +594,7 @@ export function Timeline() { - @@ -603,7 +603,7 @@ export function Timeline() { - @@ -1625,7 +1625,7 @@ function TimelineTrackContent({ {/* Clip options menu */}