mirror of
https://github.com/cfngc4594/monaco-editor-lsp-next.git
synced 2025-05-18 15:26:36 +00:00
feat(auth): update sign-in and sign-up flow with redirect support
Some checks failed
Build & Push Judge4c Docker Image / build-and-push-judge4c-docker-image (., Dockerfile, judge4c) (push) Failing after 0s
Some checks failed
Build & Push Judge4c Docker Image / build-and-push-judge4c-docker-image (., Dockerfile, judge4c) (push) Failing after 0s
This commit is contained in:
parent
799ab3ccd1
commit
5465450143
@ -9,7 +9,7 @@ import { CredentialsSignUpFormValues } from "@/components/credentials-sign-up-fo
|
|||||||
|
|
||||||
const saltRounds = 10;
|
const saltRounds = 10;
|
||||||
|
|
||||||
export async function signInWithCredentials(formData: CredentialsSignInFormValues) {
|
export async function signInWithCredentials(formData: CredentialsSignInFormValues, redirectTo?: string) {
|
||||||
try {
|
try {
|
||||||
// Parse credentials using authSchema for validation
|
// Parse credentials using authSchema for validation
|
||||||
const { email, password } = await authSchema.parseAsync(formData);
|
const { email, password } = await authSchema.parseAsync(formData);
|
||||||
@ -33,7 +33,7 @@ export async function signInWithCredentials(formData: CredentialsSignInFormValue
|
|||||||
throw new Error("Incorrect password.");
|
throw new Error("Incorrect password.");
|
||||||
}
|
}
|
||||||
|
|
||||||
await signIn("credentials", { ...formData, redirect: false });
|
await signIn("credentials", { ...formData, redirectTo, redirect: !!redirectTo });
|
||||||
return { success: true };
|
return { success: true };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return { error: error instanceof Error ? error.message : "Failed to sign in. Please try again." };
|
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." };
|
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 Link from "next/link";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { auth } from "@/lib/auth";
|
import { CodeIcon } from "lucide-react";
|
||||||
import { Code } from "lucide-react";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
interface AuthLayoutProps {
|
interface AuthLayoutProps {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@ -11,16 +9,13 @@ interface AuthLayoutProps {
|
|||||||
export default async function AuthLayout({
|
export default async function AuthLayout({
|
||||||
children
|
children
|
||||||
}: AuthLayoutProps) {
|
}: AuthLayoutProps) {
|
||||||
const session = await auth();
|
|
||||||
if (session) redirect("/");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="grid min-h-svh lg:grid-cols-2">
|
<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 flex-col gap-4 p-6 md:p-10">
|
||||||
<div className="flex justify-center gap-2 md:justify-start">
|
<div className="flex justify-center gap-2 md:justify-start">
|
||||||
<Link href="/" className="flex items-center gap-2 font-medium">
|
<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">
|
<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>
|
</div>
|
||||||
Judge4c
|
Judge4c
|
||||||
</Link>
|
</Link>
|
||||||
|
@ -1,11 +1,4 @@
|
|||||||
import {
|
import { LogOut } from "lucide-react";
|
||||||
LogIn,
|
|
||||||
LogOut,
|
|
||||||
} from "lucide-react";
|
|
||||||
import {
|
|
||||||
Avatar,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/components/ui/avatar";
|
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
@ -16,8 +9,9 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "@/components/ui/dropdown-menu";
|
} from "@/components/ui/dropdown-menu";
|
||||||
import { auth, signOut } from "@/lib/auth";
|
import { auth, signOut } from "@/lib/auth";
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
import { Skeleton } from "@/components/ui/skeleton";
|
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";
|
import { SettingsButton } from "@/components/settings-button";
|
||||||
|
|
||||||
const UserAvatar = ({ image, name }: { image: string; name: string }) => (
|
const UserAvatar = ({ image, name }: { image: string; name: string }) => (
|
||||||
@ -27,12 +21,7 @@ const UserAvatar = ({ image, name }: { image: string; name: string }) => (
|
|||||||
</Avatar>
|
</Avatar>
|
||||||
);
|
);
|
||||||
|
|
||||||
async function handleSignIn() {
|
async function handleLogOut() {
|
||||||
"use server";
|
|
||||||
redirect("/sign-in");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handleSignOut() {
|
|
||||||
"use server";
|
"use server";
|
||||||
await signOut();
|
await signOut();
|
||||||
}
|
}
|
||||||
@ -57,10 +46,7 @@ export async function AvatarButton() {
|
|||||||
{!isLoggedIn ? (
|
{!isLoggedIn ? (
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
<DropdownMenuItem onClick={handleSignIn}>
|
<LogInButton />
|
||||||
<LogIn />
|
|
||||||
Log In
|
|
||||||
</DropdownMenuItem>
|
|
||||||
</DropdownMenuGroup>
|
</DropdownMenuGroup>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@ -76,7 +62,7 @@ export async function AvatarButton() {
|
|||||||
<DropdownMenuSeparator />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuGroup>
|
<DropdownMenuGroup>
|
||||||
<SettingsButton />
|
<SettingsButton />
|
||||||
<DropdownMenuItem onClick={handleSignOut}>
|
<DropdownMenuItem onClick={handleLogOut}>
|
||||||
<LogOut />
|
<LogOut />
|
||||||
Log out
|
Log out
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
|
@ -12,18 +12,20 @@ import {
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { authSchema } from "@/lib/zod";
|
import { authSchema } from "@/lib/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useState, useTransition } from "react";
|
import { useState, useTransition } from "react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { signInWithCredentials } from "@/actions/auth";
|
import { signInWithCredentials } from "@/actions/auth";
|
||||||
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
|
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
export type CredentialsSignInFormValues = z.infer<typeof authSchema>;
|
export type CredentialsSignInFormValues = z.infer<typeof authSchema>;
|
||||||
|
|
||||||
export function CredentialsSignInForm() {
|
export function CredentialsSignInForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const redirectTo = searchParams.get("redirectTo");
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ export function CredentialsSignInForm() {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast.success("Signed In Successfully");
|
toast.success("Signed In Successfully");
|
||||||
router.push("/");
|
router.push(redirectTo || "/");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -12,18 +12,20 @@ import {
|
|||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { authSchema } from "@/lib/zod";
|
import { authSchema } from "@/lib/zod";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { useState, useTransition } from "react";
|
import { useState, useTransition } from "react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { signUpWithCredentials } from "@/actions/auth";
|
import { signUpWithCredentials } from "@/actions/auth";
|
||||||
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
|
import { EyeIcon, EyeOffIcon, MailIcon } from "lucide-react";
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
export type CredentialsSignUpFormValues = z.infer<typeof authSchema>;
|
export type CredentialsSignUpFormValues = z.infer<typeof authSchema>;
|
||||||
|
|
||||||
export function CredentialsSignUpForm() {
|
export function CredentialsSignUpForm() {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const redirectTo = searchParams.get("redirectTo");
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [isVisible, setIsVisible] = useState(false);
|
const [isVisible, setIsVisible] = useState(false);
|
||||||
|
|
||||||
@ -48,11 +50,8 @@ export function CredentialsSignUpForm() {
|
|||||||
} else {
|
} else {
|
||||||
toast.success("Account Created", {
|
toast.success("Account Created", {
|
||||||
description: "You can now sign in with your credentials",
|
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 { Button } from "@/components/ui/button";
|
||||||
|
import { signInWithGithub } from "@/actions/auth";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
|
|
||||||
export function GithubSignInForm() {
|
export function GithubSignInForm() {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
const redirectTo = searchParams.get("redirectTo");
|
||||||
|
const signInAction = signInWithGithub.bind(null, redirectTo || "/");
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form action={signInAction}>
|
||||||
action={async () => {
|
<Button variant="outline" className="w-full" type="submit">
|
||||||
"use server";
|
|
||||||
await signIn("github");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Button variant="outline" className="w-full">
|
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">
|
||||||
<path
|
<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"
|
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 { GithubSignInForm } from "@/components/github-sign-in-form";
|
||||||
import { CredentialsSignInForm } from "@/components/credentials-sign-in-form";
|
import { CredentialsSignInForm } from "@/components/credentials-sign-in-form";
|
||||||
|
|
||||||
export function SignInForm() {
|
export function SignInForm() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const handleSignUp = () => {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
router.push(`/sign-up?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex flex-col items-center gap-2 text-center">
|
<div className="flex flex-col items-center gap-2 text-center">
|
||||||
@ -19,9 +30,12 @@ export function SignInForm() {
|
|||||||
<GithubSignInForm />
|
<GithubSignInForm />
|
||||||
<div className="text-center text-sm">
|
<div className="text-center text-sm">
|
||||||
Don't have an account?{" "}
|
Don't have an account?{" "}
|
||||||
<a href="/sign-up" className="underline underline-offset-4">
|
<button
|
||||||
|
onClick={handleSignUp}
|
||||||
|
className="underline underline-offset-4"
|
||||||
|
>
|
||||||
Sign up
|
Sign up
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,7 +1,18 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import { GithubSignInForm } from "@/components/github-sign-in-form";
|
import { GithubSignInForm } from "@/components/github-sign-in-form";
|
||||||
import { CredentialsSignUpForm } from "@/components/credentials-sign-up-form";
|
import { CredentialsSignUpForm } from "@/components/credentials-sign-up-form";
|
||||||
|
|
||||||
export function SignUpForm() {
|
export function SignUpForm() {
|
||||||
|
const router = useRouter();
|
||||||
|
const searchParams = useSearchParams();
|
||||||
|
|
||||||
|
const handleSignIn = () => {
|
||||||
|
const params = new URLSearchParams(searchParams.toString());
|
||||||
|
router.push(`/sign-in?${params.toString()}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="flex flex-col items-center gap-2 text-center">
|
<div className="flex flex-col items-center gap-2 text-center">
|
||||||
@ -19,9 +30,12 @@ export function SignUpForm() {
|
|||||||
<GithubSignInForm />
|
<GithubSignInForm />
|
||||||
<div className="text-center text-sm">
|
<div className="text-center text-sm">
|
||||||
Already have an account?{" "}
|
Already have an account?{" "}
|
||||||
<a href="/sign-in" className="underline underline-offset-4">
|
<button
|
||||||
|
onClick={handleSignIn}
|
||||||
|
className="underline underline-offset-4"
|
||||||
|
>
|
||||||
Sign in
|
Sign in
|
||||||
</a>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
Loading…
Reference in New Issue
Block a user