diff options
| author | Zaid-maker <53424436+Zaid-maker@users.noreply.github.com> | 2025-06-24 16:59:44 +0500 |
|---|---|---|
| committer | Zaid-maker <53424436+Zaid-maker@users.noreply.github.com> | 2025-06-24 16:59:44 +0500 |
| commit | 62051de6c11dde40e2929c030e4418f87b137e42 (patch) | |
| tree | 3492ed2abd6a127512a6ce09543bcf770bcd53d5 /apps/web/src/app | |
| parent | 7c83b8e70b2024d79f053bd40e6f633969c2c1f5 (diff) | |
| parent | 900deab065ef17941262f48524af786cac302945 (diff) | |
| download | opencut-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.tsx | 176 | ||||
| -rw-r--r-- | apps/web/src/app/(auth)/signup/page.tsx | 201 | ||||
| -rw-r--r-- | apps/web/src/app/api/auth/[...all]/route.ts | 2 | ||||
| -rw-r--r-- | apps/web/src/app/api/waitlist/route.ts | 4 | ||||
| -rw-r--r-- | apps/web/src/app/auth/login/page.tsx | 98 | ||||
| -rw-r--r-- | apps/web/src/app/auth/signup/page.tsx | 108 | ||||
| -rw-r--r-- | apps/web/src/app/editor/editor.css | 4 | ||||
| -rw-r--r-- | apps/web/src/app/editor/page.tsx | 107 | ||||
| -rw-r--r-- | apps/web/src/app/favicon.ico | bin | 4286 -> 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 Binary files differindex 66b4ee0f..cb20127b 100644 --- a/apps/web/src/app/favicon.ico +++ b/apps/web/src/app/favicon.ico |
