feat(auth): update sign-in and sign-up flow with redirect support

This commit is contained in:
cfngc4594 2025-04-14 21:27:06 +08:00
parent 799ab3ccd1
commit 5465450143
9 changed files with 88 additions and 48 deletions

View File

@ -9,7 +9,7 @@ import { CredentialsSignUpFormValues } from "@/components/credentials-sign-up-fo
const saltRounds = 10;
export async function signInWithCredentials(formData: CredentialsSignInFormValues) {
export async function signInWithCredentials(formData: CredentialsSignInFormValues, redirectTo?: string) {
try {
// Parse credentials using authSchema for validation
const { email, password } = await authSchema.parseAsync(formData);
@ -33,7 +33,7 @@ export async function signInWithCredentials(formData: CredentialsSignInFormValue
throw new Error("Incorrect password.");
}
await signIn("credentials", { ...formData, redirect: false });
await signIn("credentials", { ...formData, redirectTo, redirect: !!redirectTo });
return { success: true };
} catch (error) {
return { error: error instanceof Error ? error.message : "Failed to sign in. Please try again." };
@ -67,3 +67,7 @@ export async function signUpWithCredentials(formData: CredentialsSignUpFormValue
return { error: error instanceof Error ? error.message : "Registration failed. Please try again." };
}
}
export async function signInWithGithub(redirectTo?: string) {
await signIn("github", { redirectTo, redirect: !!redirectTo });
}

View File

@ -1,8 +1,6 @@
import Link from "next/link";
import Image from "next/image";
import { auth } from "@/lib/auth";
import { Code } from "lucide-react";
import { redirect } from "next/navigation";
import { CodeIcon } from "lucide-react";
interface AuthLayoutProps {
children: React.ReactNode;
@ -11,16 +9,13 @@ interface AuthLayoutProps {
export default async function AuthLayout({
children
}: AuthLayoutProps) {
const session = await auth();
if (session) redirect("/");
return (
<div className="grid min-h-svh lg:grid-cols-2">
<div className="flex flex-col gap-4 p-6 md:p-10">
<div className="flex justify-center gap-2 md:justify-start">
<Link href="/" className="flex items-center gap-2 font-medium">
<div className="flex h-6 w-6 items-center justify-center rounded-md bg-primary text-primary-foreground">
<Code className="size-4" />
<CodeIcon className="size-4" />
</div>
Judge4c
</Link>

View File

@ -1,11 +1,4 @@
import {
LogIn,
LogOut,
} from "lucide-react";
import {
Avatar,
AvatarImage,
} from "@/components/ui/avatar";
import { LogOut } from "lucide-react";
import {
DropdownMenu,
DropdownMenuContent,
@ -16,8 +9,9 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { auth, signOut } from "@/lib/auth";
import { redirect } from "next/navigation";
import { Skeleton } from "@/components/ui/skeleton";
import LogInButton from "@/components/log-in-button";
import { Avatar, AvatarImage } from "@/components/ui/avatar";
import { SettingsButton } from "@/components/settings-button";
const UserAvatar = ({ image, name }: { image: string; name: string }) => (
@ -27,12 +21,7 @@ const UserAvatar = ({ image, name }: { image: string; name: string }) => (
</Avatar>
);
async function handleSignIn() {
"use server";
redirect("/sign-in");
}
async function handleSignOut() {
async function handleLogOut() {
"use server";
await signOut();
}
@ -57,10 +46,7 @@ export async function AvatarButton() {
{!isLoggedIn ? (
<DropdownMenuGroup>
<SettingsButton />
<DropdownMenuItem onClick={handleSignIn}>
<LogIn />
Log In
</DropdownMenuItem>
<LogInButton />
</DropdownMenuGroup>
) : (
<>
@ -76,7 +62,7 @@ export async function AvatarButton() {
<DropdownMenuSeparator />
<DropdownMenuGroup>
<SettingsButton />
<DropdownMenuItem onClick={handleSignOut}>
<DropdownMenuItem onClick={handleLogOut}>
<LogOut />
Log out
</DropdownMenuItem>

View File

@ -12,18 +12,20 @@ import {
import { toast } from "sonner";
import { authSchema } from "@/lib/zod";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState, useTransition } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { signInWithCredentials } from "@/actions/auth";
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
export type CredentialsSignInFormValues = z.infer<typeof authSchema>;
export function CredentialsSignInForm() {
const router = useRouter();
const searchParams = useSearchParams();
const redirectTo = searchParams.get("redirectTo");
const [isPending, startTransition] = useTransition();
const [isVisible, setIsVisible] = useState(false);
@ -47,7 +49,7 @@ export function CredentialsSignInForm() {
});
} else {
toast.success("Signed In Successfully");
router.push("/");
router.push(redirectTo || "/");
}
});
};

View File

@ -12,18 +12,20 @@ import {
import { toast } from "sonner";
import { authSchema } from "@/lib/zod";
import { useForm } from "react-hook-form";
import { useRouter } from "next/navigation";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useState, useTransition } from "react";
import { zodResolver } from "@hookform/resolvers/zod";
import { signUpWithCredentials } from "@/actions/auth";
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
import { useRouter, useSearchParams } from "next/navigation";
export type CredentialsSignUpFormValues = z.infer<typeof authSchema>;
export function CredentialsSignUpForm() {
const router = useRouter();
const searchParams = useSearchParams();
const redirectTo = searchParams.get("redirectTo");
const [isPending, startTransition] = useTransition();
const [isVisible, setIsVisible] = useState(false);
@ -48,11 +50,8 @@ export function CredentialsSignUpForm() {
} else {
toast.success("Account Created", {
description: "You can now sign in with your credentials",
action: {
label: "Go to Sign In",
onClick: () => router.push("/sign-in"),
},
});
router.push(`/sign-in?${redirectTo}`)
}
});
};

View File

@ -1,15 +1,17 @@
import { signIn } from "@/lib/auth";
"use client";
import { Button } from "@/components/ui/button";
import { signInWithGithub } from "@/actions/auth";
import { useSearchParams } from "next/navigation";
export function GithubSignInForm() {
const searchParams = useSearchParams();
const redirectTo = searchParams.get("redirectTo");
const signInAction = signInWithGithub.bind(null, redirectTo || "/");
return (
<form
action={async () => {
"use server";
await signIn("github");
}}
>
<Button variant="outline" className="w-full">
<form action={signInAction}>
<Button variant="outline" className="w-full" type="submit">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
<path
d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"

View File

@ -0,0 +1,24 @@
"use client";
import { LogIn } from "lucide-react";
import { DropdownMenuItem } from "@/components/ui/dropdown-menu";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
export default function LogInButton() {
const router = useRouter();
const pathname = usePathname();
const searchParams = useSearchParams();
const handleLogIn = () => {
const params = new URLSearchParams(searchParams.toString());
params.set("redirectTo", pathname);
router.push(`/sign-in?${params.toString()}`);
};
return (
<DropdownMenuItem onClick={handleLogIn}>
<LogIn className="mr-2 h-4 w-4" />
Log In
</DropdownMenuItem>
);
}

View File

@ -1,7 +1,18 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { GithubSignInForm } from "@/components/github-sign-in-form";
import { CredentialsSignInForm } from "@/components/credentials-sign-in-form";
export function SignInForm() {
const router = useRouter();
const searchParams = useSearchParams();
const handleSignUp = () => {
const params = new URLSearchParams(searchParams.toString());
router.push(`/sign-up?${params.toString()}`);
};
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col items-center gap-2 text-center">
@ -19,9 +30,12 @@ export function SignInForm() {
<GithubSignInForm />
<div className="text-center text-sm">
Don&apos;t have an account?{" "}
<a href="/sign-up" className="underline underline-offset-4">
<button
onClick={handleSignUp}
className="underline underline-offset-4"
>
Sign up
</a>
</button>
</div>
</div>
);

View File

@ -1,7 +1,18 @@
"use client";
import { useRouter, useSearchParams } from "next/navigation";
import { GithubSignInForm } from "@/components/github-sign-in-form";
import { CredentialsSignUpForm } from "@/components/credentials-sign-up-form";
export function SignUpForm() {
const router = useRouter();
const searchParams = useSearchParams();
const handleSignIn = () => {
const params = new URLSearchParams(searchParams.toString());
router.push(`/sign-in?${params.toString()}`);
};
return (
<div className="flex flex-col gap-6">
<div className="flex flex-col items-center gap-2 text-center">
@ -19,9 +30,12 @@ export function SignUpForm() {
<GithubSignInForm />
<div className="text-center text-sm">
Already have an account?{" "}
<a href="/sign-in" className="underline underline-offset-4">
<button
onClick={handleSignIn}
className="underline underline-offset-4"
>
Sign in
</a>
</button>
</div>
</div>
);