summaryrefslogtreecommitdiff
path: root/apps/web/src/app
diff options
context:
space:
mode:
authorZaid-maker <53424436+Zaid-maker@users.noreply.github.com>2025-06-24 16:59:44 +0500
committerZaid-maker <53424436+Zaid-maker@users.noreply.github.com>2025-06-24 16:59:44 +0500
commit62051de6c11dde40e2929c030e4418f87b137e42 (patch)
tree3492ed2abd6a127512a6ce09543bcf770bcd53d5 /apps/web/src/app
parent7c83b8e70b2024d79f053bd40e6f633969c2c1f5 (diff)
parent900deab065ef17941262f48524af786cac302945 (diff)
downloadopencut-62051de6c11dde40e2929c030e4418f87b137e42.tar.gz
Merge branch 'main' into implement-workflows
Diffstat (limited to 'apps/web/src/app')
-rw-r--r--apps/web/src/app/(auth)/login/page.tsx176
-rw-r--r--apps/web/src/app/(auth)/signup/page.tsx201
-rw-r--r--apps/web/src/app/api/auth/[...all]/route.ts2
-rw-r--r--apps/web/src/app/api/waitlist/route.ts4
-rw-r--r--apps/web/src/app/auth/login/page.tsx98
-rw-r--r--apps/web/src/app/auth/signup/page.tsx108
-rw-r--r--apps/web/src/app/editor/editor.css4
-rw-r--r--apps/web/src/app/editor/page.tsx107
-rw-r--r--apps/web/src/app/favicon.icobin4286 -> 4286 bytes
9 files changed, 444 insertions, 256 deletions
diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx
new file mode 100644
index 00000000..73c34e1e
--- /dev/null
+++ b/apps/web/src/app/(auth)/login/page.tsx
@@ -0,0 +1,176 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { signIn } from "@opencut/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<string | null>(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 (
+ <div className="flex flex-col space-y-6">
+ {error && (
+ <Alert variant="destructive">
+ <AlertTitle>Error</AlertTitle>
+ <AlertDescription>{error}</AlertDescription>
+ </Alert>
+ )}
+
+ <Button
+ onClick={handleGoogleLogin}
+ variant="outline"
+ size="lg"
+ disabled={isAnyLoading}
+ >
+ {isGoogleLoading ? (
+ <Loader2 className="animate-spin" />
+ ) : (
+ <GoogleIcon />
+ )}{" "}
+ Continue with Google
+ </Button>
+ <div className="relative">
+ <div className="absolute inset-0 flex items-center">
+ <Separator className="w-full" />
+ </div>
+ <div className="relative flex justify-center text-xs uppercase">
+ <span className="bg-background px-2 text-muted-foreground">
+ Or continue with
+ </span>
+ </div>
+ </div>
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="email">Email</Label>
+ <Input
+ id="email"
+ type="email"
+ placeholder="m@example.com"
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ disabled={isAnyLoading}
+ className="h-11"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="password">Password</Label>
+ <Input
+ id="password"
+ type="password"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ disabled={isAnyLoading}
+ className="h-11"
+ />
+ </div>
+ <Button
+ onClick={handleLogin}
+ disabled={isAnyLoading || !email || !password}
+ className="w-full h-11"
+ size="lg"
+ >
+ {isEmailLoading ? <Loader2 className="animate-spin" /> : "Sign in"}
+ </Button>
+ </div>
+ </div>
+ );
+}
+
+export default function LoginPage() {
+ const router = useRouter();
+
+ return (
+ <div className="flex h-screen items-center justify-center relative">
+ <Button
+ variant="text"
+ onClick={() => router.back()}
+ className="absolute top-6 left-6"
+ >
+ <ArrowLeft className="h-5 w-5" /> Back
+ </Button>
+ <Card className="w-[400px] shadow-lg border-0">
+ <CardHeader className="text-center pb-4">
+ <CardTitle className="text-2xl font-semibold">Welcome back</CardTitle>
+ <CardDescription className="text-base">
+ Sign in to your account to continue
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <Suspense
+ fallback={
+ <div className="text-center">
+ <Loader2 className="animate-spin" />
+ </div>
+ }
+ >
+ <LoginForm />
+ </Suspense>
+ <div className="mt-6 text-center text-sm">
+ Don't have an account?{" "}
+ <Link
+ href="/signup"
+ className="font-medium text-primary underline-offset-4 hover:underline"
+ >
+ Sign up
+ </Link>
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ );
+}
diff --git a/apps/web/src/app/(auth)/signup/page.tsx b/apps/web/src/app/(auth)/signup/page.tsx
new file mode 100644
index 00000000..1c98b9e1
--- /dev/null
+++ b/apps/web/src/app/(auth)/signup/page.tsx
@@ -0,0 +1,201 @@
+"use client";
+
+import { useRouter } from "next/navigation";
+import { signUp, signIn } from "@opencut/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<string | null>(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("/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 (
+ <div className="flex flex-col space-y-6">
+ {error && (
+ <Alert variant="destructive">
+ <AlertTitle>Error</AlertTitle>
+ <AlertDescription>{error}</AlertDescription>
+ </Alert>
+ )}
+
+ <Button
+ onClick={handleGoogleSignUp}
+ variant="outline"
+ size="lg"
+ disabled={isAnyLoading}
+ >
+ {isGoogleLoading ? (
+ <Loader2 className="animate-spin" />
+ ) : (
+ <GoogleIcon />
+ )}{" "}
+ Continue with Google
+ </Button>
+
+ <div className="relative">
+ <div className="absolute inset-0 flex items-center">
+ <Separator className="w-full" />
+ </div>
+ <div className="relative flex justify-center text-xs uppercase">
+ <span className="bg-background px-2 text-muted-foreground">
+ Or continue with
+ </span>
+ </div>
+ </div>
+
+ <div className="space-y-4">
+ <div className="space-y-2">
+ <Label htmlFor="name">Full Name</Label>
+ <Input
+ id="name"
+ type="text"
+ placeholder="John Doe"
+ value={name}
+ onChange={(e) => setName(e.target.value)}
+ disabled={isAnyLoading}
+ className="h-11"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="email">Email</Label>
+ <Input
+ id="email"
+ type="email"
+ placeholder="m@example.com"
+ value={email}
+ onChange={(e) => setEmail(e.target.value)}
+ disabled={isAnyLoading}
+ className="h-11"
+ />
+ </div>
+ <div className="space-y-2">
+ <Label htmlFor="password">Password</Label>
+ <Input
+ id="password"
+ type="password"
+ placeholder="Create a strong password"
+ value={password}
+ onChange={(e) => setPassword(e.target.value)}
+ disabled={isAnyLoading}
+ className="h-11"
+ />
+ </div>
+ <Button
+ onClick={handleSignUp}
+ disabled={isAnyLoading || !name || !email || !password}
+ className="w-full h-11"
+ size="lg"
+ >
+ {isEmailLoading ? (
+ <Loader2 className="animate-spin" />
+ ) : (
+ "Create account"
+ )}
+ </Button>
+ </div>
+ </div>
+ );
+}
+
+export default function SignUpPage() {
+ const router = useRouter();
+
+ return (
+ <div className="flex h-screen items-center justify-center relative">
+ <Button
+ variant="text"
+ onClick={() => router.back()}
+ className="absolute top-6 left-6"
+ >
+ <ArrowLeft className="h-5 w-5" /> Back
+ </Button>
+
+ <Card className="w-[400px] shadow-lg border-0">
+ <CardHeader className="text-center pb-4">
+ <CardTitle className="text-2xl font-semibold">
+ Create your account
+ </CardTitle>
+ <CardDescription className="text-base">
+ Get started with your free account today
+ </CardDescription>
+ </CardHeader>
+ <CardContent className="pt-0">
+ <Suspense
+ fallback={
+ <div className="text-center">
+ <Loader2 className="animate-spin" />
+ </div>
+ }
+ >
+ <SignUpForm />
+ </Suspense>
+ <div className="mt-6 text-center text-sm">
+ Already have an account?{" "}
+ <Link
+ href="/login"
+ className="font-medium text-primary underline-offset-4 hover:underline"
+ >
+ Sign in
+ </Link>
+ </div>
+ </CardContent>
+ </Card>
+ </div>
+ );
+}
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/app/api/waitlist/route.ts b/apps/web/src/app/api/waitlist/route.ts
index f0b1c1fb..0632616b 100644
--- a/apps/web/src/app/api/waitlist/route.ts
+++ b/apps/web/src/app/api/waitlist/route.ts
@@ -1,6 +1,6 @@
import { NextRequest, NextResponse } from "next/server";
-import { db } from "@/lib/db";
-import { waitlist } from "@/lib/db/schema";
+import { db } from "@opencut/db";
+import { waitlist } from "@opencut/db/schema";
import { eq } from "drizzle-orm";
import { nanoid } from "nanoid";
import { waitlistRateLimit } from "@/lib/rate-limit";
diff --git a/apps/web/src/app/auth/login/page.tsx b/apps/web/src/app/auth/login/page.tsx
deleted file mode 100644
index 5eadb85c..00000000
--- a/apps/web/src/app/auth/login/page.tsx
+++ /dev/null
@@ -1,98 +0,0 @@
-"use client";
-
-import { useSearchParams, useRouter } from "next/navigation";
-import { authClient } 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 Link from "next/link";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-
-function LoginForm() {
- const searchParams = useSearchParams();
- const router = useRouter();
- const redirectUrl = searchParams.get("redirect");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [error, setError] = useState<string | null>(null);
-
- const handleLogin = async () => {
- setError(null);
- const { error } = await authClient.signIn.email({
- email,
- password,
- });
-
- if (error) {
- setError(error.message || "An unexpected error occurred.");
- return;
- }
-
- router.push(redirectUrl || "/");
- };
-
- return (
- <div className="flex flex-col space-y-4">
- {error && (
- <Alert variant="destructive">
- <AlertTitle>Error</AlertTitle>
- <AlertDescription>{error}</AlertDescription>
- </Alert>
- )}
- <div className="space-y-2">
- <Label htmlFor="email">Email</Label>
- <Input
- id="email"
- type="email"
- placeholder="m@example.com"
- value={email}
- onChange={(e) => setEmail(e.target.value)}
- />
- </div>
- <div className="space-y-2">
- <Label htmlFor="password">Password</Label>
- <Input
- id="password"
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- />
- </div>
- <Button onClick={handleLogin}>Login</Button>
- </div>
- );
-}
-
-export default function LoginPage() {
- return (
- <div className="flex h-screen items-center justify-center">
- <Card className="w-[350px]">
- <CardHeader>
- <CardTitle>Login</CardTitle>
- <CardDescription>
- Enter your email and password to login.
- </CardDescription>
- </CardHeader>
- <CardContent>
- <Suspense fallback={<div>Loading...</div>}>
- <LoginForm />
- </Suspense>
- <div className="mt-4 text-center text-sm">
- Don't have an account?{" "}
- <Link href="/auth/signup" className="underline">
- Sign up
- </Link>
- </div>
- </CardContent>
- </Card>
- </div>
- );
-}
diff --git a/apps/web/src/app/auth/signup/page.tsx b/apps/web/src/app/auth/signup/page.tsx
deleted file mode 100644
index f328851c..00000000
--- a/apps/web/src/app/auth/signup/page.tsx
+++ /dev/null
@@ -1,108 +0,0 @@
-"use client";
-
-import { useSearchParams, useRouter } from "next/navigation";
-import { authClient } 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 Link from "next/link";
-import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
-
-function SignUpForm() {
- const searchParams = useSearchParams();
- const router = useRouter();
- const redirectUrl = searchParams.get("redirect");
- const [name, setName] = useState("");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [error, setError] = useState<string | null>(null);
-
- const handleSignUp = async () => {
- setError(null);
- const { error } = await authClient.signUp.email({
- name,
- email,
- password,
- });
-
- if (error) {
- setError(error.message || "An unexpected error occurred.");
- return;
- }
-
- router.push(redirectUrl || "/");
- };
-
- return (
- <div className="flex flex-col space-y-4">
- {error && (
- <Alert variant="destructive">
- <AlertTitle>Error</AlertTitle>
- <AlertDescription>{error}</AlertDescription>
- </Alert>
- )}
- <div className="space-y-2">
- <Label htmlFor="name">Name</Label>
- <Input
- id="name"
- type="text"
- placeholder="John Doe"
- value={name}
- onChange={(e) => setName(e.target.value)}
- />
- </div>
- <div className="space-y-2">
- <Label htmlFor="email">Email</Label>
- <Input
- id="email"
- type="email"
- placeholder="m@example.com"
- value={email}
- onChange={(e) => setEmail(e.target.value)}
- />
- </div>
- <div className="space-y-2">
- <Label htmlFor="password">Password</Label>
- <Input
- id="password"
- type="password"
- value={password}
- onChange={(e) => setPassword(e.target.value)}
- />
- </div>
- <Button onClick={handleSignUp}>Sign Up</Button>
- </div>
- );
-}
-
-export default function SignUpPage() {
- return (
- <div className="flex h-screen items-center justify-center">
- <Card className="w-[350px]">
- <CardHeader>
- <CardTitle>Sign Up</CardTitle>
- <CardDescription>Create an account to get started.</CardDescription>
- </CardHeader>
- <CardContent>
- <Suspense fallback={<div>Loading...</div>}>
- <SignUpForm />
- </Suspense>
- <div className="mt-4 text-center text-sm">
- Already have an account?{" "}
- <Link href="/auth/login" className="underline">
- Login
- </Link>
- </div>
- </CardContent>
- </Card>
- </div>
- );
-}
diff --git a/apps/web/src/app/editor/editor.css b/apps/web/src/app/editor/editor.css
new file mode 100644
index 00000000..f482c6de
--- /dev/null
+++ b/apps/web/src/app/editor/editor.css
@@ -0,0 +1,4 @@
+/* Prevent scroll jumping on Mac devices when using the editor */
+body {
+ overflow: hidden;
+} \ No newline at end of file
diff --git a/apps/web/src/app/editor/page.tsx b/apps/web/src/app/editor/page.tsx
index 2f825a22..e0013937 100644
--- a/apps/web/src/app/editor/page.tsx
+++ b/apps/web/src/app/editor/page.tsx
@@ -1,13 +1,14 @@
"use client";
import { useEffect } from "react";
+import "./editor.css";
import {
ResizablePanelGroup,
ResizablePanel,
ResizableHandle,
} from "../../components/ui/resizable";
import { MediaPanel } from "../../components/editor/media-panel";
-import { PropertiesPanel } from "../../components/editor/properties-panel";
+// import { PropertiesPanel } from "../../components/editor/properties-panel";
import { Timeline } from "../../components/editor/timeline";
import { PreviewPanel } from "../../components/editor/preview-panel";
import { EditorHeader } from "@/components/editor-header";
@@ -42,59 +43,71 @@ export default function Editor() {
return (
<EditorProvider>
- <div className="h-screen w-screen flex flex-col bg-background">
+ <div className="h-screen w-screen flex flex-col bg-background overflow-hidden">
<EditorHeader />
- <ResizablePanelGroup direction="vertical">
- <ResizablePanel
- defaultSize={mainContent}
- minSize={30}
- onResize={setMainContent}
- >
- {/* Main content area */}
- <ResizablePanelGroup direction="horizontal">
- {/* Tools Panel */}
- <ResizablePanel
- defaultSize={toolsPanel}
- minSize={15}
- onResize={setToolsPanel}
- >
- <MediaPanel />
- </ResizablePanel>
+ <div className="flex-1 min-h-0 min-w-0">
+ <ResizablePanelGroup direction="vertical" className="h-full w-full">
+ <ResizablePanel
+ defaultSize={mainContent}
+ minSize={30}
+ maxSize={85}
+ onResize={setMainContent}
+ className="min-h-0"
+ >
+ {/* Main content area */}
+ <ResizablePanelGroup direction="horizontal" className="h-full w-full">
+ {/* Tools Panel */}
+ <ResizablePanel
+ defaultSize={toolsPanel}
+ minSize={15}
+ maxSize={40}
+ onResize={setToolsPanel}
+ className="min-w-0"
+ >
+ <MediaPanel />
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- {/* Preview Area */}
- <ResizablePanel
- defaultSize={previewPanel}
- onResize={setPreviewPanel}
- >
- <PreviewPanel />
- </ResizablePanel>
+ {/* Preview Area */}
+ <ResizablePanel
+ defaultSize={previewPanel}
+ minSize={30}
+ onResize={setPreviewPanel}
+ className="min-w-0 min-h-0 flex-1"
+ >
+ <PreviewPanel />
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- {/* Properties Panel */}
- <ResizablePanel
- defaultSize={propertiesPanel}
- minSize={15}
- onResize={setPropertiesPanel}
- >
- <PropertiesPanel />
- </ResizablePanel>
- </ResizablePanelGroup>
- </ResizablePanel>
+ {/* Properties Panel - Hidden for now but ready */}
+ {/* <ResizablePanel
+ defaultSize={propertiesPanel}
+ minSize={15}
+ maxSize={40}
+ onResize={setPropertiesPanel}
+ className="min-w-0"
+ >
+ <PropertiesPanel />
+ </ResizablePanel> */}
+ </ResizablePanelGroup>
+ </ResizablePanel>
- <ResizableHandle withHandle />
+ <ResizableHandle withHandle />
- {/* Timeline */}
- <ResizablePanel
- defaultSize={timeline}
- minSize={15}
- onResize={setTimeline}
- >
- <Timeline />
- </ResizablePanel>
- </ResizablePanelGroup>
+ {/* Timeline */}
+ <ResizablePanel
+ defaultSize={timeline}
+ minSize={15}
+ maxSize={70}
+ onResize={setTimeline}
+ className="min-h-0"
+ >
+ <Timeline />
+ </ResizablePanel>
+ </ResizablePanelGroup>
+ </div>
</div>
</EditorProvider>
);
diff --git a/apps/web/src/app/favicon.ico b/apps/web/src/app/favicon.ico
index 66b4ee0f..cb20127b 100644
--- a/apps/web/src/app/favicon.ico
+++ b/apps/web/src/app/favicon.ico
Binary files differ