mirror of
https://github.com/massbug/judge4c.git
synced 2025-05-17 14:56:36 +00:00
feat(auth): update sign-in and sign-up flow with redirect support
This commit is contained in:
parent
799ab3ccd1
commit
5465450143
@ -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 });
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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 || "/");
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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}`)
|
||||
}
|
||||
});
|
||||
};
|
||||
|
@ -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"
|
||||
|
24
src/components/log-in-button.tsx
Normal file
24
src/components/log-in-button.tsx
Normal 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>
|
||||
);
|
||||
}
|
@ -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'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>
|
||||
);
|
||||
|
@ -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>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user