From b772c6f68e81c4c88186219dc841a14ab278cf50 Mon Sep 17 00:00:00 2001 From: YaoSiQian <2229561981@qq.com> Date: Tue, 24 Jun 2025 05:00:34 +0800 Subject: [PATCH 01/11] fix(docker): web service --- README.md | 14 ++++++++++++++ apps/web/Dockerfile | 36 ++++++++++++++++++++++++++++++++++++ docker-compose.yaml | 41 ++++++++++++++++++++++++++++++++++++++--- 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 apps/web/Dockerfile diff --git a/README.md b/README.md index 3ccb2b0..998acd3 100644 --- a/README.md +++ b/README.md @@ -47,6 +47,20 @@ A free, open-source video editor for web, desktop, and mobile. 4. **Open in browser:** Visit [http://localhost:3000](http://localhost:3000) +## Run with Docker +1. **Prepare environment variables:** + Edit [docker-compose.yaml](https://github.com/OpenCut-app/OpenCut/blob/main/docker-compose.yaml#L57-L64) +2. **Build and run:** + ```bash + docker-compose up -d --build + ``` +3. *(Optional)* **Migrate database:** + ```bash + docker-compose exec web bun run db:migrate + ``` +4. **Open in browser:** + Visit [http://localhost:3000](http://localhost:3000) + ## Contributing ## License diff --git a/apps/web/Dockerfile b/apps/web/Dockerfile new file mode 100644 index 0000000..ca1f098 --- /dev/null +++ b/apps/web/Dockerfile @@ -0,0 +1,36 @@ +FROM oven/bun:latest AS base + +# Install dependencies +FROM base AS deps +WORKDIR /app +COPY package.json bun.lock ./ +RUN bun install --frozen-lockfile + +# Build the application +FROM base AS builder +WORKDIR /app +COPY --from=deps /app/node_modules ./node_modules +COPY . . +RUN bun run build + +# Production image +FROM base AS runner +WORKDIR /app + +ENV NODE_ENV=production + +RUN addgroup --system --gid 1001 nodejs +RUN adduser --system --uid 1001 nextjs + +COPY --from=builder /app/public ./public +COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static + +USER nextjs + +EXPOSE 3000 + +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +CMD ["bun", "server.js"] \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index c1c61a4..c654926 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -18,7 +18,8 @@ services: start_period: 10s redis: - image: redis + image: redis:7-alpine + restart: unless-stopped ports: - "6379:6379" healthcheck: @@ -36,12 +37,46 @@ services: SRH_MODE: env SRH_TOKEN: example_token SRH_CONNECTION_STRING: "redis://redis:6379" + depends_on: + redis: + condition: service_healthy healthcheck: test: ["CMD-SHELL", "wget --spider -q http://127.0.0.1:80 || exit 1"] interval: 30s timeout: 10s retries: 5 start_period: 10s - -volumes: + web: + build: + context: ./apps/web + dockerfile: ./apps/web/Dockerfile + restart: unless-stopped + ports: + - "3000:3000" + environment: + - NODE_ENV=production + - DATABASE_URL=postgresql://opencut:opencutthegoat@db:5432/opencut + - BETTER_AUTH_URL=http://localhost:3000 + - BETTER_AUTH_SECRET=your-production-secret-key-here + - UPSTASH_REDIS_REST_URL=http://serverless-redis-http:80 + - UPSTASH_REDIS_REST_TOKEN=example_token + - GOOGLE_CLIENT_ID=${GOOGLE_CLIENT_ID} + - GOOGLE_CLIENT_SECRET=${GOOGLE_CLIENT_SECRET} + depends_on: + db: + condition: service_healthy + serverless-redis-http: + condition: service_healthy + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"] + interval: 30s + timeout: 10s + retries: 5 + start_period: 30s + +volumes: postgres_data: + +networks: + default: + name: opencut-network From 79ac2c28238642436bc57a55671731460ccf2640 Mon Sep 17 00:00:00 2001 From: YaoSiQian Date: Tue, 24 Jun 2025 05:06:21 +0800 Subject: [PATCH 02/11] style(README): hot fix at line break --- README.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 998acd3..1c377b6 100644 --- a/README.md +++ b/README.md @@ -27,11 +27,13 @@ A free, open-source video editor for web, desktop, and mobile. ## Getting Started 1. **Clone the repository:** + ```bash git clone cd OpenCut ``` 2. **Install dependencies:** + ```bash cd apps/web npm install @@ -39,30 +41,37 @@ A free, open-source video editor for web, desktop, and mobile. bun install ``` 3. **Run the development server:** + ```bash npm run dev # or, with Bun bun run dev ``` -4. **Open in browser:** +4. **Open in browser:** + Visit [http://localhost:3000](http://localhost:3000) ## Run with Docker -1. **Prepare environment variables:** +1. **Prepare environment variables:** + Edit [docker-compose.yaml](https://github.com/OpenCut-app/OpenCut/blob/main/docker-compose.yaml#L57-L64) 2. **Build and run:** + ```bash docker-compose up -d --build ``` 3. *(Optional)* **Migrate database:** + ```bash docker-compose exec web bun run db:migrate ``` 4. **Open in browser:** + Visit [http://localhost:3000](http://localhost:3000) ## Contributing +Visit [CONTRIBUTING.md](.github/CONTRIBUTING.md) ## License -MIT [Details](LICENSE) +[MIT LICENSE](LICENSE) From 63b698fe12338d441becbb47561a1f5705f579b9 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 01:48:06 +0200 Subject: [PATCH 03/11] chore: add .cursorignore file to exclude specific files from cursor tracking --- .cursorignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .cursorignore diff --git a/.cursorignore b/.cursorignore new file mode 100644 index 0000000..633521a --- /dev/null +++ b/.cursorignore @@ -0,0 +1 @@ +!apps/web/.env.example \ No newline at end of file From 69566ffa83b9f192397073f37de0d9a8a9437fef Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 01:48:46 +0200 Subject: [PATCH 04/11] fix: update hero section text from "CapCut alternative" to "video editor" --- apps/web/src/components/landing/hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index f2c70f5..b02ce1b 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -84,7 +84,7 @@ export function Hero({ signupCount }: HeroProps) { The open source

- CapCut alternative. + video editor

From f2879be973e9f62b5c2c14e7a3e982bf0492f573 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 01:55:40 +0200 Subject: [PATCH 05/11] docs: enhance contributing guidelines with detailed setup instructions and environment variable configuration --- .github/CONTRIBUTING.md | 87 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 8 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 793d175..b9e2275 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -13,33 +13,104 @@ Thank you for your interest in contributing to OpenCut! This document provides g ## Development Setup ### Prerequisites -- Node.js 18+ + +- Node.js 18+ - Bun (latest version) - Docker (for local database) ### Local Development -1. Copy `.env.example` to `.env.local` and configure your environment variables -2. Start the database: `docker-compose up -d` (run from project root) -3. Navigate to the web app: `cd apps/web` -4. Run database migrations: `bun run db:migrate` -5. Start the development server: `bun run dev` + +1. Start the database and Redis services: + + ```bash + # From project root + docker-compose up -d + ``` + +2. Navigate to the web app directory: + + ```bash + cd apps/web + ``` + +3. Copy `.env.example` to `.env.local`: + + ```bash + # Unix/Linux/Mac + cp .env.example .env.local + + # Windows Command Prompt + copy .env.example .env.local + + # Windows PowerShell + Copy-Item .env.example .env.local + ``` + +4. Configure required environment variables in `.env.local`: + + **Required Variables:** + + ```bash + # Database (matches docker-compose.yaml) + DATABASE_URL="postgresql://opencut:opencutthegoat@localhost:5432/opencut" + + # Generate a secure secret for Better Auth + BETTER_AUTH_SECRET="your-generated-secret-here" + BETTER_AUTH_URL="http://localhost:3000" + + # Redis (matches docker-compose.yaml) + UPSTASH_REDIS_REST_URL="http://localhost:8079" + UPSTASH_REDIS_REST_TOKEN="example_token" + + # Development + NODE_ENV="development" + ``` + + **Generate BETTER_AUTH_SECRET:** + + ```bash + # Unix/Linux/Mac + openssl rand -base64 32 + + # Windows PowerShell (simple method) + [System.Web.Security.Membership]::GeneratePassword(32, 0) + + # Cross-platform (using Node.js) + node -e "console.log(require('crypto').randomBytes(32).toString('base64'))" + + # Or use an online generator: https://generate-secret.vercel.app/32 + ``` + + **Optional Variables (for Google OAuth):** + + ```bash + # Only needed if you want to test Google login + GOOGLE_CLIENT_ID="your-google-client-id" + GOOGLE_CLIENT_SECRET="your-google-client-secret" + ``` + +5. Run database migrations: `bun run db:migrate` +6. Start the development server: `bun run dev` ## How to Contribute ### Reporting Bugs + - Use the bug report template - Include steps to reproduce - Provide screenshots if applicable ### Suggesting Features + - Use the feature request template - Explain the use case - Consider implementation details ### Code Contributions + 1. Create a new branch: `git checkout -b feature/your-feature-name` 2. Make your changes -3. Navigate to the web app directory: `cd apps/web` +3. Navigate to the web app directory: `cd apps/web` 4. Run the linter: `bun run lint` 5. Format your code: `bunx biome format --write .` 6. Commit your changes with a descriptive message @@ -66,4 +137,4 @@ Thank you for your interest in contributing to OpenCut! This document provides g - Follow our Code of Conduct - Help others in discussions and issues -Thank you for contributing! \ No newline at end of file +Thank you for contributing! From 37ceaaed5ec4f5484aadb31c1798708bf5e992ee Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:09:43 +0200 Subject: [PATCH 06/11] docs: update README with contributing guidelines and quick start instructions for contributors --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 3ccb2b0..3dce048 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,14 @@ A free, open-source video editor for web, desktop, and mobile. ## Contributing +We welcome contributions! Please see our [Contributing Guide](.github/CONTRIBUTING.md) for detailed setup instructions and development guidelines. + +Quick start for contributors: + +- Fork the repo and clone locally +- Follow the setup instructions in CONTRIBUTING.md +- Create a feature branch and submit a PR + ## License MIT [Details](LICENSE) From c64480ac79f9ccda7b5e108bdbb9503d972cddd3 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:15:00 +0200 Subject: [PATCH 07/11] chore: update logo image in public assets --- apps/web/public/logo.png | Bin 7088 -> 1789 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/web/public/logo.png b/apps/web/public/logo.png index a0af7a14cf205662b9e71b523033e8d24f5b7b9e..fc9e7902c12cf51004226e5253ecc30980f3c6b4 100644 GIT binary patch literal 1789 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 From b5e8e8ce628dee51bcdb9bbeb8139333e5169d54 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:19:11 +0200 Subject: [PATCH 08/11] chore: update favicon.ico for improved branding --- apps/web/src/app/favicon.ico | Bin 4286 -> 4286 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/apps/web/src/app/favicon.ico b/apps/web/src/app/favicon.ico index 66b4ee0fcd06df7e0c1bbf4530d6d3012fc130a0..cb20127bf2a20d8f63093fbc22d530ab32ec489b 100644 GIT binary patch literal 4286 zcmeHJK?;LF3{1fX^wf)p9{Y?wz?+}!NBoEQ1O32uM;KV85mR0RtZ~Jgn=w-BVr{_{VYF z=6iew;S*=T5Rc~p9KQxMaDC*%XY{nN;x!)jt$bEIb+l^bv*I;f_N{zYJax2c<+I{7 zUG}YfRy=jIn)~|)Upe;==MsA0E;y?(b|1A_uX;$K9eT+1`%4cw|9{X!sbvCX0&ynb I_h{Dm0CD6vNdN!< literal 4286 zcmeI0!A`^wc~t? zl2}?+33d-MOUjZg&=APV`eQ8;<7b%9tBt%&kJ3%$dx1t;8Spk>85D=rm%#hc=<4Gpx=MEmUXI?{7qh8BH5`#}*Z@-{Tl z=ANWl6h$(h&t*EDTFhoMS*=#7u{7c%<}03DICdD?FKPbqcr49kQ>xXf1VJE`N=54R zx=bdMv~b&oj~HUfAr}s~G7Rnaq>p%V^#T2cQmG`P(I^?dxyDCKG7qkdK5S{nacuhx zN1;%VMx!C!Zdcmvw)A>EJGL9a#23b$7-GpG7Y?{ut(L&;vM>y#R;$?@@|Vk{D<#vv zSS)NG(1#Olm+>38cjRgc;@IC*0zkhtce|_sG*I(AqeT}u7HT*a0FEs+S z0y&4QKd8ep>W{4&DChdicm3(R{$>y0F3k3Q2($ChMBDfN%j^DA=NbBdJ#p^)?}0I= NkNicDv!OXq?GF>Tk8uD1 From 2b06fd97d601e15c4dfb3008fcfd69bbb711a4d6 Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:20:41 +0200 Subject: [PATCH 09/11] fix: 404 on auth pages --- apps/web/src/app/{(auth) => auth}/login/page.tsx | 0 apps/web/src/app/{(auth) => auth}/signup/page.tsx | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename apps/web/src/app/{(auth) => auth}/login/page.tsx (100%) rename apps/web/src/app/{(auth) => auth}/signup/page.tsx (100%) diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/auth/login/page.tsx similarity index 100% rename from apps/web/src/app/(auth)/login/page.tsx rename to apps/web/src/app/auth/login/page.tsx diff --git a/apps/web/src/app/(auth)/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx similarity index 100% rename from apps/web/src/app/(auth)/signup/page.tsx rename to apps/web/src/app/auth/signup/page.tsx From 6d29228c8ca1dc554505e566cc266c717df9bbdb Mon Sep 17 00:00:00 2001 From: Maze Winther Date: Tue, 24 Jun 2025 02:45:59 +0200 Subject: [PATCH 10/11] style: adjust margin in hero section for improved layout --- apps/web/src/components/landing/hero.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/landing/hero.tsx b/apps/web/src/components/landing/hero.tsx index b02ce1b..714d432 100644 --- a/apps/web/src/components/landing/hero.tsx +++ b/apps/web/src/components/landing/hero.tsx @@ -89,7 +89,7 @@ export function Hero({ signupCount }: HeroProps) { Date: Tue, 24 Jun 2025 02:48:14 +0200 Subject: [PATCH 11/11] feat: implement unified AuthForm component for login and signup pages --- apps/web/bun.lock | 1 + apps/web/package.json | 3 +- apps/web/src/app/auth/login/page.tsx | 161 +---------- apps/web/src/app/auth/signup/page.tsx | 180 +----------- apps/web/src/components/auth-form.tsx | 398 ++++++++++++++++++++++++++ 5 files changed, 405 insertions(+), 338 deletions(-) create mode 100644 apps/web/src/components/auth-form.tsx diff --git a/apps/web/bun.lock b/apps/web/bun.lock index 141e7db..3aba715 100644 --- a/apps/web/bun.lock +++ b/apps/web/bun.lock @@ -41,6 +41,7 @@ "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.1", + "zod": "^3.25.67", "zustand": "^5.0.2", }, "devDependencies": { diff --git a/apps/web/package.json b/apps/web/package.json index 581aa02..0a2aa20 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -2,7 +2,7 @@ "name": "next-template", "version": "0.1.0", "private": true, - "packageManager": "bun@1.2.2", + "packageManager": "bun@1.2.2", "scripts": { "dev": "next dev --turbopack", "build": "next build", @@ -51,6 +51,7 @@ "tailwind-merge": "^2.5.5", "tailwindcss-animate": "^1.0.7", "vaul": "^1.1.1", + "zod": "^3.25.67", "zustand": "^5.0.2" }, "devDependencies": { diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx index ebf2eb3..18117f4 100644 --- a/apps/web/src/app/auth/login/page.tsx +++ b/apps/web/src/app/auth/login/page.tsx @@ -1,162 +1,5 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { signIn } from "@/lib/auth-client"; -import { Button } from "@/components/ui/button"; -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/components/ui/card"; -import { Suspense, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -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"; - -function LoginForm() { - const router = useRouter(); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [isEmailLoading, setIsEmailLoading] = useState(false); - const [isGoogleLoading, setIsGoogleLoading] = useState(false); - - const handleLogin = async () => { - setError(null); - setIsEmailLoading(true); - - const { error } = await signIn.email({ - email, - password, - }); - - if (error) { - setError(error.message || "An unexpected error occurred."); - setIsEmailLoading(false); - return; - } - - router.push("/editor"); - }; - - const handleGoogleLogin = async () => { - setError(null); - setIsGoogleLoading(true); - - try { - await signIn.social({ - provider: "google", - }); - router.push("/editor"); - } catch (error) { - setError("Failed to sign in with Google. Please try again."); - setIsGoogleLoading(false); - } - }; - - const isAnyLoading = isEmailLoading || isGoogleLoading; - - return ( -
- {error && ( - - Error - {error} - - )} - - -
-
- -
-
- Or continue with -
-
-
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
- ); -} +import { AuthForm } from "@/components/auth-form"; export default function LoginPage() { - const router = useRouter(); - - return ( -
- - - - Welcome back - - Sign in to your account to continue - - - - - -
}> - - -
- Don't have an account?{" "} - - Sign up - -
- - - - ); + return ; } diff --git a/apps/web/src/app/auth/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx index 3d125a0..109ae5c 100644 --- a/apps/web/src/app/auth/signup/page.tsx +++ b/apps/web/src/app/auth/signup/page.tsx @@ -1,181 +1,5 @@ -"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 { Suspense, useState } from "react"; -import { Input } from "@/components/ui/input"; -import { Label } from "@/components/ui/label"; -import { Separator } from "@/components/ui/separator"; -import Link from "next/link"; -import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -import { Loader2, ArrowLeft } from "lucide-react"; -import { GoogleIcon } from "@/components/icons"; - -function SignUpForm() { - const router = useRouter(); - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [error, setError] = useState(null); - const [isEmailLoading, setIsEmailLoading] = useState(false); - const [isGoogleLoading, setIsGoogleLoading] = useState(false); - - const handleSignUp = async () => { - setError(null); - setIsEmailLoading(true); - - const { error } = await signUp.email({ - name, - email, - password, - }); - - if (error) { - setError(error.message || "An unexpected error occurred."); - setIsEmailLoading(false); - return; - } - - router.push("/auth/login"); - }; - - const handleGoogleSignUp = async () => { - setError(null); - setIsGoogleLoading(true); - - try { - await signIn.social({ - provider: "google", - }); - - router.push("/editor"); - } catch (error) { - setError("Failed to sign up with Google. Please try again."); - setIsGoogleLoading(false); - } - }; - - const isAnyLoading = isEmailLoading || isGoogleLoading; - - return ( -
- {error && ( - - Error - {error} - - )} - - - -
-
- -
-
- Or continue with -
-
- -
-
- - setName(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setEmail(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
-
- - setPassword(e.target.value)} - disabled={isAnyLoading} - className="h-11" - /> -
- -
-
- ); -} +import { AuthForm } from "@/components/auth-form"; export default function SignUpPage() { - const router = useRouter(); - - return ( -
- - - - - Create your account - - Get started with your free account today - - - - - -
}> - - -
- Already have an account?{" "} - - Sign in - -
- - - - ); + return ; } diff --git a/apps/web/src/components/auth-form.tsx b/apps/web/src/components/auth-form.tsx new file mode 100644 index 0000000..182137e --- /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} + +
+
+
+
+ ); +}