diff --git a/src/app/(marketing)/login/page.tsx b/src/app/(marketing)/login/page.tsx index cbe611b..26f1a17 100644 --- a/src/app/(marketing)/login/page.tsx +++ b/src/app/(marketing)/login/page.tsx @@ -2,8 +2,8 @@ import { LoginForm } from "@/features/auth/components/login-form" export default function Page() { return ( -
-
+
+
diff --git a/src/app/(marketing)/signup/page.tsx b/src/app/(marketing)/signup/page.tsx index f079a4d..5219b59 100644 --- a/src/app/(marketing)/signup/page.tsx +++ b/src/app/(marketing)/signup/page.tsx @@ -2,8 +2,8 @@ import { SignupForm } from "@/features/auth/components/signup-form" export default function Page() { return ( -
-
+
+
diff --git a/src/features/auth/components/auth-code-form.tsx b/src/features/auth/components/auth-code-form.tsx new file mode 100644 index 0000000..d0a734b --- /dev/null +++ b/src/features/auth/components/auth-code-form.tsx @@ -0,0 +1,153 @@ +"use client" + +import { Lock } from "lucide-react" +import Link from "next/link" +import { Controller, useForm } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" +import * as z from "zod" + +import { cn } from "@/shared/utils" +import { Button } from "@/shared/ui/button" +import { + Field, + FieldDescription, + FieldError, + FieldGroup, + FieldLabel, + FieldSeparator, +} from "@/shared/ui/field" +import { Input } from "@/shared/ui/input" + +const codeSchema = z.object({ + code: z + .string() + .trim() + .regex(/^\d{6}$/, { message: "Enter the 6-digit code" }), +}) + +type AuthCodeFormProps = React.ComponentProps<"div"> & { + email: string + mode: "login" | "signup" + onDifferentAccount?: () => void + onResendCode?: () => void + onSubmitCode?: (code: string) => void | Promise +} + +export function AuthCodeForm({ + className, + email, + mode, + onDifferentAccount, + onResendCode, + onSubmitCode, + ...props +}: AuthCodeFormProps) { + const form = useForm>({ + resolver: zodResolver(codeSchema), + defaultValues: { + code: "", + }, + }) + + const actionLabel = mode === "login" ? "Log in" : "Sign up" + const flowLabel = mode === "login" ? "login" : "sign up" + const switchLabel = + mode === "login" + ? "Log in to a different account" + : "Sign up with a different account" + + const onSubmit = async (data: z.infer) => { + await onSubmitCode?.(data.code) + } + + return ( +
+
+

+ Check your inbox +

+

+ Enter the 6-digit code we sent to{" "} + {email} to finish + your {flowLabel}. +

+
+ +
+ + ( + + + 6-digit code + +
+ +
+ +
+ )} + /> + + + + + + +
+
+ +
+ {onDifferentAccount ? ( + + ) : ( + + {switchLabel} + + )} +
+ + + Use the code sent to your email address to continue. + +
+ ) +} diff --git a/src/features/auth/components/auth-google-button.tsx b/src/features/auth/components/auth-google-button.tsx new file mode 100644 index 0000000..476291f --- /dev/null +++ b/src/features/auth/components/auth-google-button.tsx @@ -0,0 +1,42 @@ +import { Button } from "@/shared/ui/button" + +function GoogleMark() { + return ( + + ) +} + +export function AuthGoogleButton({ label }: { label: string }) { + return ( + + ) +} diff --git a/src/features/auth/components/login-form.tsx b/src/features/auth/components/login-form.tsx index 7f6dec8..2291b24 100644 --- a/src/features/auth/components/login-form.tsx +++ b/src/features/auth/components/login-form.tsx @@ -1,126 +1,115 @@ "use client" +import { useState } from "react" import { cn } from "@/shared/utils" import { Button } from "@/shared/ui/button" -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/shared/ui/card" import { Field, FieldDescription, + FieldError, FieldGroup, FieldLabel, + FieldSeparator, } from "@/shared/ui/field" import { Input } from "@/shared/ui/input" import { Controller, useForm } from "react-hook-form" import * as z from "zod" import { zodResolver } from "@hookform/resolvers/zod" -import { auth } from "@/infrastructure/auth/auth" -import { authClient } from "@/infrastructure/auth/client" import Link from "next/link" +import { AuthCodeForm } from "@/features/auth/components/auth-code-form" +import { AuthGoogleButton } from "@/features/auth/components/auth-google-button" const formSchema = z.object({ email: z.email({ message: "Invalid email address", }), - password: z - .string({ - message: "Password is required", - }) - .min(8, { error: "Must be more than 8 characters" }), }) export function LoginForm({ className, ...props }: React.ComponentProps<"div">) { + const [loginEmail, setLoginEmail] = useState(null) const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { email: "", - password: "", }, }) const onSubmit = async (data: z.infer) => { - try { - await authClient.signIn.email(data) - } catch (error) { - console.error(error) - } + setLoginEmail(data.email) + } + + if (loginEmail) { + return ( + setLoginEmail(null)} + className={className} + {...props} + /> + ) } return ( -
- - - Login to your account - - Enter your email below to login to your account - - - -
- - ( - <> - - Email - - - - )} - /> +
+

+ Log in to continue your learning journey +

+ + + +
+ ( + + + Email + + + + + )} + /> + + + + + other log in option + +
- ( - - - - - )} - /> + +
+ - - - - - Don't have an account?{" "} - Sign up - - - - - - +
+ + Don't have an account? + + + Sign up + +
) } diff --git a/src/features/auth/components/signup-form.tsx b/src/features/auth/components/signup-form.tsx index f912cd3..6a3c682 100644 --- a/src/features/auth/components/signup-form.tsx +++ b/src/features/auth/components/signup-form.tsx @@ -1,183 +1,165 @@ "use client" + +import { useState } from "react" +import Link from "next/link" +import { Controller, useForm } from "react-hook-form" +import * as z from "zod" +import { zodResolver } from "@hookform/resolvers/zod" + import { cn } from "@/shared/utils" import { Button } from "@/shared/ui/button" -import { - Card, - CardContent, - CardDescription, - CardHeader, - CardTitle, -} from "@/shared/ui/card" import { Field, FieldDescription, + FieldError, FieldGroup, FieldLabel, + FieldSeparator, } from "@/shared/ui/field" import { Input } from "@/shared/ui/input" -import { Controller, useForm } from "react-hook-form" -import * as z from "zod" -import { zodResolver } from "@hookform/resolvers/zod" -import { authClient } from "@/infrastructure/auth/client" -import Link from "next/link" +import { AuthCodeForm } from "@/features/auth/components/auth-code-form" +import { AuthGoogleButton } from "@/features/auth/components/auth-google-button" -const formSchema = z - .object({ - name: z.string({ - message: "Name is required", - }), - email: z.email({ - message: "Invalid email address", - }), - password: z - .string({ - message: "Password is required", - }) - .min(8, { error: "Must be more than 8 characters" }), - confirmPassword: z - .string({ - message: "Please confirm your password", - }) - .min(8, { error: "Must be more than 8 characters" }), - }) - .refine((data) => data.password === data.confirmPassword, { - message: "Passwords do not match", - path: ["confirmPassword"], - }) +const formSchema = z.object({ + name: z.string().trim().min(1, { message: "Full name is required" }), + email: z.email({ + message: "Invalid email address", + }), +}) + +type SignupStep = { + name: string + email: string +} | null export function SignupForm({ className, ...props }: React.ComponentProps<"div">) { + const [signupStep, setSignupStep] = useState(null) const form = useForm>({ resolver: zodResolver(formSchema), defaultValues: { name: "", email: "", - password: "", - confirmPassword: "", }, }) const onSubmit = async (data: z.infer) => { - try { - const { error } = await authClient.signUp.email({ - name: data.name, - email: data.email, - password: data.password, - callbackURL: "/", - }) - if (error) { - console.error(error) - } - } catch (error) { - console.error(error) - } + setSignupStep(data) + } + + if (signupStep) { + return ( + setSignupStep(null)} + className={className} + {...props} + /> + ) } return ( -
- - - Create an account - - Enter your information below to create your account - - - -
- - ( - - Full Name - - - )} - /> +
+

+ Sign up with email +

+ + + +
+ ( + + + Full Name + + + + + )} + /> + + ( + + + Email + + + + + )} + /> + + - ( - - Email - - - We'll use this to contact you. We will not share your - email with anyone else. - - - )} - /> + + other sign up option + +
- ( - - Password - - - Must be at least 8 characters long. - - - )} - /> + +
+ - ( - - - Confirm Password - - - - Please confirm your password. - - - )} - /> + + By signing up, you agree to our{" "} + + Terms of Use + {" "} + and{" "} + + Privacy Policy + + . + - - - - - Already have an account?{" "} - Sign in - - - - - - +
+ + Already have an account? + + + Log in + +
) } diff --git a/src/features/marketing/components/conditional-layout-sections.tsx b/src/features/marketing/components/conditional-layout-sections.tsx index d249580..ebf0116 100644 --- a/src/features/marketing/components/conditional-layout-sections.tsx +++ b/src/features/marketing/components/conditional-layout-sections.tsx @@ -8,7 +8,7 @@ export function ConditionalLayoutSections() { const pathname = usePathname() const showFaq = pathname === "/" || pathname === "/about" - const showCta = pathname !== "/donation" + const showCta = !["/donation", "/login", "/signup"].includes(pathname) return ( <>