mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-09 14:00:15 -04:00
Add basic authentication and redirection handling
This commit is contained in:
parent
7dc5aca9ee
commit
755d1523d0
|
@ -5,22 +5,34 @@ import { redirect } from "next/navigation";
|
||||||
|
|
||||||
import { createClient } from "@/utils/supabase/server";
|
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
|
// type-casting here for convenience
|
||||||
// in practice, you should validate your inputs
|
// in practice, you should validate your inputs
|
||||||
const data = {
|
const data = {
|
||||||
email: username,
|
email,
|
||||||
password: password,
|
password,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error } = await supabase.auth.signInWithPassword(data);
|
const { error } = await supabase.auth.signInWithPassword(data);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
redirect("/auth/error");
|
return "Incorrect email/password";
|
||||||
}
|
}
|
||||||
|
|
||||||
revalidatePath("/resource", "layout");
|
revalidatePath("/resource", "layout");
|
||||||
redirect("/resource");
|
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");
|
||||||
|
}
|
||||||
|
|
|
@ -5,18 +5,39 @@ import Button from "@/components/Button";
|
||||||
import Input from "@/components/Input";
|
import Input from "@/components/Input";
|
||||||
import InlineLink from "@/components/InlineLink";
|
import InlineLink from "@/components/InlineLink";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import PasswordInput from "@/components/auth/PasswordInput";
|
import PasswordInput from "@/components/auth/PasswordInput";
|
||||||
import ErrorBanner from "@/components/auth/ErrorBanner";
|
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() {
|
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 [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [emailError, setEmailError] = useState("");
|
const [emailError, setEmailError] = useState("");
|
||||||
const [passwordError, setPasswordError] = useState("");
|
const [passwordError, setPasswordError] = useState("");
|
||||||
|
const [loginError, setLoginError] = useState("");
|
||||||
|
|
||||||
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
setEmail(event.currentTarget.value);
|
setEmail(event.currentTarget.value);
|
||||||
|
setEmail;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handlePasswordChange = (
|
const handlePasswordChange = (
|
||||||
|
@ -25,23 +46,33 @@ export default function Page() {
|
||||||
setPassword(event.currentTarget.value);
|
setPassword(event.currentTarget.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
const handleClick = async (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||||
// Priority: Incorrect combo > Missing email > Missing password
|
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) {
|
if (password.trim().length === 0) {
|
||||||
setEmailError("Please enter your password.");
|
console.log(password);
|
||||||
event.preventDefault();
|
setPasswordError("Please enter your password.");
|
||||||
}
|
return;
|
||||||
// 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setPasswordError("");
|
||||||
|
|
||||||
|
const error = await login(email, password);
|
||||||
|
setLoginError(error);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -60,7 +91,7 @@ export default function Page() {
|
||||||
type="email"
|
type="email"
|
||||||
valid={emailError == ""}
|
valid={emailError == ""}
|
||||||
title="Email"
|
title="Email"
|
||||||
placeholder="janedoe@gmail.com"
|
placeholder="Enter Email"
|
||||||
onChange={handleEmailChange}
|
onChange={handleEmailChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
|
@ -70,6 +101,7 @@ export default function Page() {
|
||||||
<div className="mb-6">
|
<div className="mb-6">
|
||||||
<PasswordInput
|
<PasswordInput
|
||||||
title="Password"
|
title="Password"
|
||||||
|
placeholder="Enter Password"
|
||||||
valid={passwordError == ""}
|
valid={passwordError == ""}
|
||||||
onChange={handlePasswordChange}
|
onChange={handlePasswordChange}
|
||||||
/>
|
/>
|
||||||
|
@ -82,6 +114,8 @@ export default function Page() {
|
||||||
</InlineLink>
|
</InlineLink>
|
||||||
<Button onClick={handleClick}>Login</Button>
|
<Button onClick={handleClick}>Login</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{loginError && <ErrorBanner heading={loginError} />}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@ import InlineLink from "@/components/InlineLink";
|
||||||
import Paper from "@/components/auth/Paper";
|
import Paper from "@/components/auth/Paper";
|
||||||
// import { Metadata } from 'next'
|
// import { Metadata } from 'next'
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ChangeEvent, useState } from "react";
|
import { useState } from "react";
|
||||||
|
|
||||||
// export const metadata: Metadata = {
|
// export const metadata: Metadata = {
|
||||||
// title: 'Login',
|
// title: 'Login',
|
||||||
|
|
|
@ -3,6 +3,10 @@
|
||||||
import Sidebar from "@/components/resource/Sidebar";
|
import Sidebar from "@/components/resource/Sidebar";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { ChevronDoubleRightIcon } from "@heroicons/react/24/outline";
|
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({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
|
@ -10,6 +14,26 @@ export default function RootLayout({
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}) {
|
}) {
|
||||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
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 (
|
return (
|
||||||
<div className="flex-row">
|
<div className="flex-row">
|
||||||
|
@ -27,13 +51,21 @@ export default function RootLayout({
|
||||||
</button>
|
</button>
|
||||||
{/* sidebar */}
|
{/* sidebar */}
|
||||||
<div
|
<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>
|
</div>
|
||||||
{/* page ui */}
|
{/* page ui */}
|
||||||
<div
|
<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}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -11,9 +11,11 @@ import { UserProfile } from "./UserProfile";
|
||||||
|
|
||||||
interface SidebarProps {
|
interface SidebarProps {
|
||||||
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
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 (
|
return (
|
||||||
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
|
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
|
||||||
{/* button to close sidebar */}
|
{/* button to close sidebar */}
|
||||||
|
@ -29,7 +31,7 @@ const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen }) => {
|
||||||
<div className="flex flex-col space-y-8">
|
<div className="flex flex-col space-y-8">
|
||||||
{/* user + logout button */}
|
{/* user + logout button */}
|
||||||
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
|
<div className="flex items-center p-4 space-x-2 border border-gray-200 rounded-md ">
|
||||||
<UserProfile />
|
<UserProfile name={name} email={email} />
|
||||||
</div>
|
</div>
|
||||||
{/* navigation menu */}
|
{/* navigation menu */}
|
||||||
<div className="flex flex-col space-y-2">
|
<div className="flex flex-col space-y-2">
|
||||||
|
|
|
@ -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 (
|
return (
|
||||||
<div className="flex flex-col items-start space-y-2">
|
<div className="flex flex-col items-start space-y-2">
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<span className="text-sm font-semibold text-gray-800">
|
<span className="text-sm font-semibold text-gray-800">
|
||||||
Compass Center
|
{name}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-gray-500">cssgunc@gmail.com</span>
|
<span className="text-xs text-gray-500">{email}</span>
|
||||||
</div>
|
</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
|
Sign out
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,6 +3,9 @@ const nextConfig = {
|
||||||
images: {
|
images: {
|
||||||
domains: ["notioly.com"],
|
domains: ["notioly.com"],
|
||||||
},
|
},
|
||||||
|
experimental: {
|
||||||
|
serverActions: true
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = nextConfig;
|
module.exports = nextConfig;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user