Add basic authentication and redirection handling

This commit is contained in:
pmoharana-cmd 2024-04-22 10:47:04 -04:00
parent 7dc5aca9ee
commit 755d1523d0
7 changed files with 124 additions and 31 deletions

View File

@ -5,22 +5,34 @@ import { redirect } from "next/navigation";
import { createClient } from "@/utils/supabase/server";
export async function login(username: string, password: string) {
const supabase = createClient();
const supabase = createClient();
export async function login(email: string, password: string) {
// type-casting here for convenience
// in practice, you should validate your inputs
const data = {
email: username,
password: password,
email,
password,
};
const { error } = await supabase.auth.signInWithPassword(data);
if (error) {
redirect("/auth/error");
return "Incorrect email/password";
}
revalidatePath("/resource", "layout");
redirect("/resource");
}
export async function signOut() {
const { data, error } = await supabase.auth.getUser();
if (error || !data?.user) {
redirect("auth/login");
}
supabase.auth.signOut();
revalidatePath("/resource", "layout");
redirect("/auth/login");
}

View File

@ -5,18 +5,39 @@ import Button from "@/components/Button";
import Input from "@/components/Input";
import InlineLink from "@/components/InlineLink";
import Image from "next/image";
import { useState } from "react";
import { useEffect, useState } from "react";
import PasswordInput from "@/components/auth/PasswordInput";
import ErrorBanner from "@/components/auth/ErrorBanner";
import { login } from "../actions";
import { createClient } from "@/utils/supabase/client";
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
useEffect(() => {
const supabase = createClient();
async function checkUser() {
const { data } = await supabase.auth.getUser();
if (data.user) {
router.push("/resource");
}
}
checkUser();
}, [router]);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [emailError, setEmailError] = useState("");
const [passwordError, setPasswordError] = useState("");
const [loginError, setLoginError] = useState("");
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setEmail(event.currentTarget.value);
setEmail;
};
const handlePasswordChange = (
@ -25,23 +46,33 @@ export default function Page() {
setPassword(event.currentTarget.value);
};
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
// Priority: Incorrect combo > Missing email > Missing password
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
event.preventDefault();
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (email.trim().length === 0) {
console.log(email);
setEmailError("Please enter your email.");
return;
}
if (!emailRegex.test(email)) {
setEmailError("Please enter a valid email address.");
return;
}
setEmailError("");
if (password.trim().length === 0) {
setEmailError("Please enter your password.");
event.preventDefault();
}
// This shouldn't happen, <input type="email"> already provides validation, but just in case.
if (email.trim().length === 0) {
setPasswordError("Please enter your email.");
event.preventDefault();
}
// Placeholder for incorrect email + password combo.
if (email === "incorrect@gmail.com" && password) {
setPasswordError("Incorrect password.");
event.preventDefault();
console.log(password);
setPasswordError("Please enter your password.");
return;
}
setPasswordError("");
const error = await login(email, password);
setLoginError(error);
};
return (
@ -60,7 +91,7 @@ export default function Page() {
type="email"
valid={emailError == ""}
title="Email"
placeholder="janedoe@gmail.com"
placeholder="Enter Email"
onChange={handleEmailChange}
required
/>
@ -70,6 +101,7 @@ export default function Page() {
<div className="mb-6">
<PasswordInput
title="Password"
placeholder="Enter Password"
valid={passwordError == ""}
onChange={handlePasswordChange}
/>
@ -82,6 +114,8 @@ export default function Page() {
</InlineLink>
<Button onClick={handleClick}>Login</Button>
</div>
{loginError && <ErrorBanner heading={loginError} />}
</>
);
}

View File

@ -7,7 +7,7 @@ import InlineLink from "@/components/InlineLink";
import Paper from "@/components/auth/Paper";
// import { Metadata } from 'next'
import Image from "next/image";
import { ChangeEvent, useState } from "react";
import { useState } from "react";
// export const metadata: Metadata = {
// title: 'Login',

View File

@ -3,6 +3,10 @@
import Sidebar from "@/components/resource/Sidebar";
import React, { useState } from "react";
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
import { createClient } from "@/utils/supabase/client";
import { useEffect } from "react";
import { User } from "@supabase/supabase-js";
import { useRouter } from "next/navigation";
export default function RootLayout({
children,
@ -10,6 +14,26 @@ export default function RootLayout({
children: React.ReactNode;
}) {
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const [user, setUser] = useState<User>();
const router = useRouter();
useEffect(() => {
const supabase = createClient();
async function getUser() {
const { data, error } = await supabase.auth.getUser();
if (error || data.user === null) {
router.push("auth/login");
return;
}
setUser(data.user);
console.log(data.user);
}
getUser();
}, [router]);
return (
<div className="flex-row">
@ -27,13 +51,21 @@ export default function RootLayout({
</button>
{/* sidebar */}
<div
className={`absolute inset-y-0 left-0 transform ${isSidebarOpen ? "translate-x-0" : "-translate-x-full"} w-64 transition duration-300 ease-in-out`}
className={`absolute inset-y-0 left-0 transform ${
isSidebarOpen ? "translate-x-0" : "-translate-x-full"
} w-64 transition duration-300 ease-in-out`}
>
<Sidebar setIsSidebarOpen={setIsSidebarOpen} />
<Sidebar
name={""}
email={(user && user.email) ?? "No email found!"}
setIsSidebarOpen={setIsSidebarOpen}
/>
</div>
{/* page ui */}
<div
className={`flex-1 transition duration-300 ease-in-out ${isSidebarOpen ? "ml-64" : "ml-0"}`}
className={`flex-1 transition duration-300 ease-in-out ${
isSidebarOpen ? "ml-64" : "ml-0"
}`}
>
{children}
</div>

View File

@ -11,9 +11,11 @@ import { UserProfile } from "./UserProfile";
interface SidebarProps {
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
name: string;
email: string;
}
const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen, name, email }) => {
return (
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
{/* button to close sidebar */}
@ -29,7 +31,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
<div className="flex flex-col space-y-8">
{/* user + logout button */}
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
<UserProfile />
<UserProfile name={name} email={email} />
</div>
{/* navigation menu */}
<div className="flex flex-col space-y-2">

View File

@ -1,13 +1,23 @@
export const UserProfile = () => {
import { signOut } from "@/app/auth/actions";
interface UserProfileProps {
name: string;
email: string;
}
export const UserProfile = ({ name, email }: UserProfileProps) => {
return (
<div className="flex flex-col items-start space-y-2">
<div className="flex flex-col">
<span className="text-sm font-semibold text-gray-800">
Compass Center
{name}
</span>
<span className="text-xs text-gray-500">cssgunc@gmail.com</span>
<span className="text-xs text-gray-500">{email}</span>
</div>
<button className="text-red-600 font-semibold text-xs hover:underline mt-1">
<button
onClick={signOut}
className="text-red-600 font-semibold text-xs hover:underline mt-1"
>
Sign out
</button>
</div>

View File

@ -3,6 +3,9 @@ const nextConfig = {
images: {
domains: ["notioly.com"],
},
experimental: {
serverActions: true
}
};
module.exports = nextConfig;