mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-03 19:40:16 -04:00
Add loading animations and authentication handling
This commit is contained in:
parent
e13100e0f7
commit
f0fabead01
|
@ -15,7 +15,7 @@ openapi_tags = {
|
|||
# TODO: Add security using HTTP Bearer Tokens
|
||||
# TODO: Enable authorization by passing user uuid to API
|
||||
# TODO: Create custom exceptions
|
||||
@api.get("all", response_model=List[User], tags=["Users"])
|
||||
@api.get("/all", response_model=List[User], tags=["Users"])
|
||||
def get_all(user_id: str, user_svc: UserService = Depends()):
|
||||
subject = user_svc.get_user_by_uuid(user_id)
|
||||
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
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 { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import User, { Role } from "@/utils/models/User";
|
||||
import Loading from "@/components/auth/Loading";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
|
@ -10,33 +15,86 @@ export default function RootLayout({
|
|||
children: React.ReactNode;
|
||||
}) {
|
||||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const router = useRouter();
|
||||
const [user, setUser] = useState<User>();
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
|
||||
console.log(data, error);
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
router.push("auth/login");
|
||||
return;
|
||||
}
|
||||
|
||||
const userData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/user?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const user: User = await userData.json();
|
||||
|
||||
if (user.role !== Role.ADMIN) {
|
||||
console.log(
|
||||
`Accessed admin page but incorrect permissions: ${user.username} ${user.role}`
|
||||
);
|
||||
router.push("auth/login");
|
||||
return;
|
||||
}
|
||||
|
||||
setUser(user);
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex-row">
|
||||
{/* button to open sidebar */}
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||
aria-label={"Open sidebar"}
|
||||
>
|
||||
{
|
||||
!isSidebarOpen && (
|
||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||
) // Icon for closing the sidebar
|
||||
}
|
||||
</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`}
|
||||
>
|
||||
<Sidebar setIsSidebarOpen={setIsSidebarOpen} />
|
||||
</div>
|
||||
{/* page ui */}
|
||||
<div
|
||||
className={`flex-1 transition duration-300 ease-in-out ${isSidebarOpen ? "ml-64" : "ml-0"}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{user ? (
|
||||
<div>
|
||||
{/* button to open sidebar */}
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||
aria-label={"Open sidebar"}
|
||||
>
|
||||
{
|
||||
!isSidebarOpen && (
|
||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||
) // Icon for closing the sidebar
|
||||
}
|
||||
</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`}
|
||||
>
|
||||
<Sidebar
|
||||
setIsSidebarOpen={setIsSidebarOpen}
|
||||
name={user.username}
|
||||
email={user.email}
|
||||
admin={user.role === Role.ADMIN}
|
||||
/>
|
||||
</div>
|
||||
{/* page ui */}
|
||||
<div
|
||||
className={`flex-1 transition duration-300 ease-in-out ${
|
||||
isSidebarOpen ? "ml-64" : "ml-0"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -2,15 +2,43 @@
|
|||
|
||||
import { PageLayout } from "@/components/PageLayout";
|
||||
import { Table } from "@/components/Table/Index";
|
||||
import User from "@/utils/models/User";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
import { UsersIcon } from "@heroicons/react/24/solid";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
export default function Page() {
|
||||
const [users, setUsers] = useState<User[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
async function getUser() {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
|
||||
if (error) {
|
||||
console.log("Accessed admin page but not logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
const userListData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/user/all?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
const users: User[] = await userListData.json();
|
||||
|
||||
setUsers(users);
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col">
|
||||
{/* icon + title */}
|
||||
<PageLayout title="Users" icon={<UsersIcon />}>
|
||||
<Table />
|
||||
<Table users={users} />
|
||||
</PageLayout>
|
||||
</div>
|
||||
);
|
||||
|
|
14
compass/app/api/user/all/route.ts
Normal file
14
compass/app/api/user/all/route.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function GET(request: Request) {
|
||||
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/user/all`;
|
||||
|
||||
console.log(apiEndpoint);
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const uuid = searchParams.get("uuid");
|
||||
|
||||
const data = await fetch(`${apiEndpoint}?user_id=${uuid}`);
|
||||
|
||||
return NextResponse.json(await data.json(), { status: data.status });
|
||||
}
|
|
@ -1,6 +1,5 @@
|
|||
// pages/index.tsx
|
||||
"use client";
|
||||
|
||||
import Button from "@/components/Button";
|
||||
import Input from "@/components/Input";
|
||||
import InlineLink from "@/components/InlineLink";
|
||||
|
@ -14,30 +13,26 @@ 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 [isLoading, setIsLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
const supabase = createClient();
|
||||
async function checkUser() {
|
||||
const { data } = await supabase.auth.getUser();
|
||||
if (data.user) {
|
||||
router.push("/resource");
|
||||
}
|
||||
}
|
||||
checkUser();
|
||||
}, [router]);
|
||||
|
||||
const handleEmailChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setEmail(event.currentTarget.value);
|
||||
setEmail;
|
||||
};
|
||||
|
||||
const handlePasswordChange = (
|
||||
|
@ -51,28 +46,28 @@ export default function Page() {
|
|||
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) {
|
||||
console.log(password);
|
||||
setPasswordError("Please enter your password.");
|
||||
return;
|
||||
}
|
||||
|
||||
setPasswordError("");
|
||||
|
||||
setIsLoading(true);
|
||||
const error = await login(email, password);
|
||||
setLoginError(error);
|
||||
setIsLoading(false);
|
||||
|
||||
if (error) {
|
||||
setLoginError(error);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -83,13 +78,11 @@ export default function Page() {
|
|||
width={100}
|
||||
height={91}
|
||||
/>
|
||||
|
||||
<h1 className="font-bold text-2xl text-purple-800">Login</h1>
|
||||
|
||||
<div className="mb-6">
|
||||
<Input
|
||||
type="email"
|
||||
valid={emailError == ""}
|
||||
valid={emailError === ""}
|
||||
title="Email"
|
||||
placeholder="Enter Email"
|
||||
onChange={handleEmailChange}
|
||||
|
@ -97,24 +90,28 @@ export default function Page() {
|
|||
/>
|
||||
</div>
|
||||
{emailError && <ErrorBanner heading={emailError} />}
|
||||
|
||||
<div className="mb-6">
|
||||
<PasswordInput
|
||||
title="Password"
|
||||
placeholder="Enter Password"
|
||||
valid={passwordError == ""}
|
||||
valid={passwordError === ""}
|
||||
onChange={handlePasswordChange}
|
||||
/>
|
||||
</div>
|
||||
{passwordError && <ErrorBanner heading={passwordError} />}
|
||||
|
||||
<div className="flex flex-col items-left space-y-4">
|
||||
<InlineLink href="/auth/forgot_password">
|
||||
Forgot password?
|
||||
</InlineLink>
|
||||
<Button onClick={handleClick}>Login</Button>
|
||||
<Button onClick={handleClick} disabled={isLoading}>
|
||||
<div className="flex items-center justify-center">
|
||||
{isLoading && (
|
||||
<div className="w-4 h-4 border-2 border-white border-t-purple-500 rounded-full animate-spin mr-2"></div>
|
||||
)}
|
||||
{isLoading ? "Logging in..." : "Login"}
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
{loginError && <ErrorBanner heading={loginError} />}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -1,4 +1,9 @@
|
|||
"use client";
|
||||
|
||||
import "../styles/globals.css";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect } from "react";
|
||||
import { createClient } from "@/utils/supabase/client";
|
||||
|
||||
export default function RootLayout({
|
||||
// Layouts must accept a children prop.
|
||||
|
|
|
@ -6,4 +6,6 @@ export default function Page() {
|
|||
const router = useRouter();
|
||||
|
||||
router.push("/auth/login");
|
||||
|
||||
return <h1>GO TO LOGIN PAGE (/auth/login)</h1>;
|
||||
}
|
||||
|
|
|
@ -4,8 +4,10 @@ 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";
|
||||
import User, { Role } from "@/utils/models/User";
|
||||
import Loading from "@/components/auth/Loading";
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: {
|
||||
|
@ -14,9 +16,11 @@ export default function RootLayout({
|
|||
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
|
||||
const [user, setUser] = useState<User>();
|
||||
const router = useRouter();
|
||||
|
||||
useEffect(() => {
|
||||
const supabase = createClient();
|
||||
async function getUser() {
|
||||
const supabase = createClient();
|
||||
|
||||
const { data, error } = await supabase.auth.getUser();
|
||||
|
||||
console.log(data, error);
|
||||
|
@ -26,48 +30,60 @@ export default function RootLayout({
|
|||
router.push("auth/login");
|
||||
return;
|
||||
}
|
||||
setUser(data.user);
|
||||
|
||||
const userData = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_HOST}/api/user?uuid=${data.user.id}`
|
||||
);
|
||||
|
||||
setUser(await userData.json());
|
||||
}
|
||||
|
||||
getUser();
|
||||
}, [router]);
|
||||
|
||||
return (
|
||||
<div className="flex-row">
|
||||
{/* button to open sidebar */}
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||
aria-label={"Open sidebar"}
|
||||
>
|
||||
{
|
||||
!isSidebarOpen && (
|
||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||
) // Icon for closing the sidebar
|
||||
}
|
||||
</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`}
|
||||
>
|
||||
<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"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
{user ? (
|
||||
<div>
|
||||
{/* button to open sidebar */}
|
||||
<button
|
||||
onClick={() => setIsSidebarOpen(!isSidebarOpen)}
|
||||
className={`fixed z-20 p-2 text-gray-500 hover:text-gray-800 left-0`}
|
||||
aria-label={"Open sidebar"}
|
||||
>
|
||||
{
|
||||
!isSidebarOpen && (
|
||||
<ChevronDoubleRightIcon className="h-5 w-5" />
|
||||
) // Icon for closing the sidebar
|
||||
}
|
||||
</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`}
|
||||
>
|
||||
<Sidebar
|
||||
name={user.username}
|
||||
email={user.email}
|
||||
setIsSidebarOpen={setIsSidebarOpen}
|
||||
admin={user.role === Role.ADMIN}
|
||||
/>
|
||||
</div>
|
||||
{/* page ui */}
|
||||
<div
|
||||
className={`flex-1 transition duration-300 ease-in-out ${
|
||||
isSidebarOpen ? "ml-64" : "ml-0"
|
||||
}`}
|
||||
>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<Loading />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,7 @@ import { FunctionComponent, ReactNode } from "react";
|
|||
type ButtonProps = {
|
||||
children: ReactNode;
|
||||
onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void;
|
||||
type?: "button" | "submit" | "reset"; // specify possible values for type
|
||||
type?: "button" | "submit" | "reset";
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
|
@ -13,11 +13,11 @@ const Button: FunctionComponent<ButtonProps> = ({
|
|||
disabled,
|
||||
onClick,
|
||||
}) => {
|
||||
const buttonClassName = `inline-block rounded border ${
|
||||
const buttonClassName = `inline-flex items-center justify-center rounded border ${
|
||||
disabled
|
||||
? "bg-gray-400 text-gray-600 cursor-not-allowed"
|
||||
: "border-purple-600 bg-purple-600 text-white hover:bg-transparent hover:text-purple-600 focus:outline-none focus:ring active:text-purple-500"
|
||||
} px-4 py-1 text-md font-semibold w-20 h-10 text-center`;
|
||||
} px-4 py-2 text-md font-semibold w-full sm:w-auto`;
|
||||
|
||||
return (
|
||||
<button
|
||||
|
@ -26,7 +26,9 @@ const Button: FunctionComponent<ButtonProps> = ({
|
|||
type={type}
|
||||
disabled={disabled}
|
||||
>
|
||||
{children}
|
||||
<div className="flex items-center justify-center space-x-2">
|
||||
{children}
|
||||
</div>
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// for showcasing to compass
|
||||
|
||||
import usersImport from "./users.json";
|
||||
import users from "./users.json";
|
||||
import {
|
||||
Cell,
|
||||
ColumnDef,
|
||||
|
@ -32,22 +32,7 @@ import {
|
|||
} from "@heroicons/react/24/solid";
|
||||
import TagsInput from "../TagsInput/Index";
|
||||
import { rankItem } from "@tanstack/match-sorter-utils";
|
||||
import { TableCell } from "./TableCell";
|
||||
import { PrimaryTableCell } from "./PrimaryTableCell";
|
||||
|
||||
const usersExample = usersImport as unknown as User[];
|
||||
|
||||
type User = {
|
||||
id: number;
|
||||
created_at: any;
|
||||
username: string;
|
||||
role: "administrator" | "employee" | "volunteer";
|
||||
email: string;
|
||||
program: "domestic" | "economic" | "community";
|
||||
experience: number;
|
||||
group?: string;
|
||||
visible: boolean;
|
||||
};
|
||||
import User from "@/utils/models/User";
|
||||
|
||||
// For search
|
||||
const fuzzyFilter = (
|
||||
|
@ -66,17 +51,17 @@ const fuzzyFilter = (
|
|||
return itemRank.passed;
|
||||
};
|
||||
|
||||
export const Table = () => {
|
||||
export const Table = ({ users }: { users: User[] }) => {
|
||||
const columnHelper = createColumnHelper<User>();
|
||||
|
||||
useEffect(() => {
|
||||
const sortedUsers = [...usersExample].sort((a, b) =>
|
||||
const sortedUsers = [...users].sort((a, b) =>
|
||||
a.visible === b.visible ? 0 : a.visible ? -1 : 1
|
||||
);
|
||||
setData(sortedUsers);
|
||||
}, []);
|
||||
}, [users]);
|
||||
|
||||
const deleteUser = (userId) => {
|
||||
const deleteUser = (userId: number) => {
|
||||
console.log(data);
|
||||
setData((currentData) =>
|
||||
currentData.filter((user) => user.id !== userId)
|
||||
|
@ -188,10 +173,10 @@ export const Table = () => {
|
|||
}),
|
||||
];
|
||||
|
||||
const [data, setData] = useState<User[]>([...usersExample]);
|
||||
const [data, setData] = useState<User[]>([...users]);
|
||||
|
||||
const addUser = () => {
|
||||
setData([...data, {}]);
|
||||
setData([...data]);
|
||||
};
|
||||
|
||||
// Searching
|
||||
|
|
43
compass/components/auth/Loading.module.css
Normal file
43
compass/components/auth/Loading.module.css
Normal file
|
@ -0,0 +1,43 @@
|
|||
/* components/Loading.module.css */
|
||||
.loadingOverlay {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.8);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.loadingContent {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loadingTitle {
|
||||
font-size: 2rem;
|
||||
font-weight: bold;
|
||||
color: #5b21b6;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.loadingSpinner {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
border: 4px solid #5b21b6;
|
||||
border-top: 4px solid #fff;
|
||||
border-radius: 50%;
|
||||
animation: spin 1s linear infinite;
|
||||
margin: 2rem auto;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
22
compass/components/auth/Loading.tsx
Normal file
22
compass/components/auth/Loading.tsx
Normal file
|
@ -0,0 +1,22 @@
|
|||
// components/Loading.js
|
||||
import styles from "./Loading.module.css";
|
||||
import Image from "next/image";
|
||||
|
||||
const Loading = () => {
|
||||
return (
|
||||
<div className={styles.loadingOverlay}>
|
||||
<div className={styles.loadingContent}>
|
||||
<Image
|
||||
src="/logo.png"
|
||||
alt="Compass Center logo."
|
||||
width={100}
|
||||
height={91}
|
||||
/>
|
||||
<h1 className={styles.loadingTitle}>Loading...</h1>
|
||||
<div className={styles.loadingSpinner}></div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Loading;
|
|
@ -13,9 +13,15 @@ interface SidebarProps {
|
|||
setIsSidebarOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
name: string;
|
||||
email: string;
|
||||
admin: boolean;
|
||||
}
|
||||
|
||||
const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen, name, email }) => {
|
||||
const Sidebar: React.FC<SidebarProps> = ({
|
||||
setIsSidebarOpen,
|
||||
name,
|
||||
email,
|
||||
admin,
|
||||
}) => {
|
||||
return (
|
||||
<div className="w-64 h-full border border-gray-200 bg-gray-50 px-4">
|
||||
{/* button to close sidebar */}
|
||||
|
@ -39,12 +45,38 @@ const Sidebar: React.FC<SidebarProps> = ({ setIsSidebarOpen, name, email }) => {
|
|||
Pages
|
||||
</h4>
|
||||
<nav className="flex flex-col">
|
||||
<SidebarItem icon={<HomeIcon />} text="Home" />
|
||||
<SidebarItem icon={<BookmarkIcon />} text="Resources" />
|
||||
<SidebarItem icon={<ClipboardIcon />} text="Services" />
|
||||
{admin && (
|
||||
<SidebarItem
|
||||
icon={<HomeIcon />}
|
||||
text="Admin"
|
||||
active={true}
|
||||
redirect="/admin"
|
||||
/>
|
||||
)}
|
||||
|
||||
<SidebarItem
|
||||
icon={<HomeIcon />}
|
||||
text="Home"
|
||||
active={true}
|
||||
redirect="/resource"
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={<BookmarkIcon />}
|
||||
text="Resources"
|
||||
active={true}
|
||||
redirect="/resource"
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={<ClipboardIcon />}
|
||||
text="Services"
|
||||
active={true}
|
||||
redirect="/service"
|
||||
/>
|
||||
<SidebarItem
|
||||
icon={<BookOpenIcon />}
|
||||
text="Training Manuals"
|
||||
active={true}
|
||||
redirect="/training-manuals"
|
||||
/>
|
||||
</nav>
|
||||
</div>
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
import Link from "next/link";
|
||||
|
||||
interface SidebarItemProps {
|
||||
icon: React.ReactElement;
|
||||
text: string;
|
||||
active: boolean;
|
||||
redirect: string;
|
||||
}
|
||||
|
||||
export const SidebarItem: React.FC<SidebarItemProps> = ({
|
||||
icon,
|
||||
text,
|
||||
active,
|
||||
redirect,
|
||||
}) => {
|
||||
return (
|
||||
<a
|
||||
href="#"
|
||||
<Link
|
||||
href={redirect}
|
||||
className={
|
||||
active
|
||||
? "flex items-center p-2 space-x-2 bg-gray-200 rounded-md"
|
||||
: "flex items-center p-2 space-x-2 hover:bg-gray-200 rounded-md"
|
||||
? "flex items-center p-2 my-1 space-x-2 bg-gray-200 rounded-md"
|
||||
: "flex items-center p-2 my-1 space-x-2 hover:bg-gray-200 rounded-md"
|
||||
}
|
||||
>
|
||||
<span className="h-5 text-gray-500 w-5">{icon}</span>
|
||||
<span className="flex-grow font-medium text-xs text-gray-500">
|
||||
{text}
|
||||
</span>
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -20,4 +20,5 @@ export default interface User {
|
|||
program: Program[];
|
||||
role: Role;
|
||||
created_at: Date;
|
||||
visible: boolean;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user