summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
Diffstat (limited to 'apps/web')
-rw-r--r--apps/web/src/app/(auth)/login/page.tsx216
-rw-r--r--apps/web/src/app/(auth)/signup/page.tsx259
-rw-r--r--apps/web/src/hooks/auth/useLogin.ts60
-rw-r--r--apps/web/src/hooks/auth/useSignUp.ts65
4 files changed, 326 insertions, 274 deletions
diff --git a/apps/web/src/app/(auth)/login/page.tsx b/apps/web/src/app/(auth)/login/page.tsx
index 755784fa..ec79bd7f 100644
--- a/apps/web/src/app/(auth)/login/page.tsx
+++ b/apps/web/src/app/(auth)/login/page.tsx
@@ -1,7 +1,6 @@
"use client";
import { useRouter } from "next/navigation";
-import { signIn } from "@opencut/auth/client";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -10,7 +9,7 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import { Suspense, useState } from "react";
+import { memo, Suspense } from "react";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Separator } from "@/components/ui/separator";
@@ -18,121 +17,22 @@ 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 { useLogin } from "@/hooks/auth/useLogin";
-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",
- callbackURL: "/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 LoginPage = () => {
const router = useRouter();
+ const {
+ email,
+ setEmail,
+ password,
+ setPassword,
+ error,
+ isAnyLoading,
+ isEmailLoading,
+ isGoogleLoading,
+ handleLogin,
+ handleGoogleLogin,
+ } = useLogin();
return (
<div className="flex h-screen items-center justify-center relative">
@@ -158,19 +58,85 @@ export default function LoginPage() {
</div>
}
>
- <LoginForm />
+ <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>
+ <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>
</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>
);
}
+
+export default memo(LoginPage);
diff --git a/apps/web/src/app/(auth)/signup/page.tsx b/apps/web/src/app/(auth)/signup/page.tsx
index 1c98b9e1..e9705213 100644
--- a/apps/web/src/app/(auth)/signup/page.tsx
+++ b/apps/web/src/app/(auth)/signup/page.tsx
@@ -1,7 +1,6 @@
"use client";
import { useRouter } from "next/navigation";
-import { signUp, signIn } from "@opencut/auth/client";
import { Button } from "@/components/ui/button";
import {
Card,
@@ -10,151 +9,32 @@ import {
CardHeader,
CardTitle,
} from "@/components/ui/card";
-import { Suspense, useState } from "react";
+import { memo, Suspense } 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 { ArrowLeft, Loader2 } from "lucide-react";
import { GoogleIcon } from "@/components/icons";
+import { useSignUp } from "@/hooks/auth/useSignUp";
-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 SignUpPage = () => {
const router = useRouter();
+ const {
+ name,
+ setName,
+ email,
+ setEmail,
+ password,
+ setPassword,
+ error,
+ isAnyLoading,
+ isEmailLoading,
+ isGoogleLoading,
+ handleSignUp,
+ handleGoogleSignUp,
+ } = useSignUp();
return (
<div className="flex h-screen items-center justify-center relative">
@@ -165,7 +45,6 @@ export default function SignUpPage() {
>
<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">
@@ -183,19 +62,101 @@ export default function SignUpPage() {
</div>
}
>
- <SignUpForm />
+ <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>
+ <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>
</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>
);
}
+
+export default memo(SignUpPage);
diff --git a/apps/web/src/hooks/auth/useLogin.ts b/apps/web/src/hooks/auth/useLogin.ts
new file mode 100644
index 00000000..3737deb3
--- /dev/null
+++ b/apps/web/src/hooks/auth/useLogin.ts
@@ -0,0 +1,60 @@
+import { useCallback, useState } from "react";
+import { useRouter } from "next/navigation";
+import { signIn } from "@opencut/auth/client";
+
+export function useLogin() {
+ 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 = useCallback(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");
+ }, [router, email, password]);
+
+ const handleGoogleLogin = async () => {
+ setError(null);
+ setIsGoogleLoading(true);
+
+ try {
+ await signIn.social({
+ provider: "google",
+ callbackURL: "/editor",
+ });
+ } catch (error) {
+ setError("Failed to sign in with Google. Please try again.");
+ setIsGoogleLoading(false);
+ }
+ };
+
+ const isAnyLoading = isEmailLoading || isGoogleLoading;
+
+ return {
+ email,
+ setEmail,
+ password,
+ setPassword,
+ error,
+ isEmailLoading,
+ isGoogleLoading,
+ isAnyLoading,
+ handleLogin,
+ handleGoogleLogin,
+ };
+}
diff --git a/apps/web/src/hooks/auth/useSignUp.ts b/apps/web/src/hooks/auth/useSignUp.ts
new file mode 100644
index 00000000..4cd2850a
--- /dev/null
+++ b/apps/web/src/hooks/auth/useSignUp.ts
@@ -0,0 +1,65 @@
+import { useState, useCallback } from "react";
+import { useRouter } from "next/navigation";
+import { signUp, signIn } from "@opencut/auth/client";
+
+export function useSignUp() {
+ 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 = useCallback(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");
+ }, [name, email, password, router]);
+
+ const handleGoogleSignUp = useCallback(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);
+ }
+ }, [router]);
+
+ const isAnyLoading = isEmailLoading || isGoogleLoading;
+
+ return {
+ name,
+ setName,
+ email,
+ setEmail,
+ password,
+ setPassword,
+ error,
+ isEmailLoading,
+ isGoogleLoading,
+ isAnyLoading,
+ handleSignUp,
+ handleGoogleSignUp,
+ };
+} \ No newline at end of file