feat: implement sidebar component and replace admin header with sidebar in admin layout

implement zustand as store for rooms
This commit is contained in:
Rémi 2025-01-05 02:38:13 +01:00
parent d45f79a3d9
commit 434414d99d
12 changed files with 191 additions and 86 deletions

View File

@ -26,6 +26,7 @@
"next-themes": "^0.4.4",
"react": "19.0.0-rc-66855b96-20241106",
"react-dom": "19.0.0-rc-66855b96-20241106",
"react-icons": "^5.4.0",
"zustand": "^5.0.2"
},
"devDependencies": {

10
src/app/admin/layout.tsx Normal file
View File

@ -0,0 +1,10 @@
import { Sidebar } from "@/app/components/Sidebar";
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<div>
<Sidebar />
<main>{children}</main>
</div>
);
}

View File

@ -1,10 +1,10 @@
import { Metadata } from "next";
import { AdminHeader } from "../components/Header/admin";
import { Sidebar } from "../components/Sidebar";
export const metadata: Metadata = {
title: "Toogether Admin",
};
export default async function AdminPage() {
return <AdminHeader />;
return <Sidebar />;
}

View File

@ -1,32 +0,0 @@
"use client";
export const AdminHeader = () => {
return (
<>
<button
data-drawer-target="default-sidebar"
data-drawer-toggle="default-sidebar"
aria-controls="default-sidebar"
type="button"
className="inline-flex items-center p-2 mt-2 ms-3 text-sm text-gray-500 rounded-lg sm:hidden hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-gray-200 dark:text-gray-400 dark:hover:bg-gray-700 dark:focus:ring-gray-600"
>
<span className="sr-only">Open sidebar</span>
<p>test</p>
</button>
<aside className="fixed top-0 left-0 z-40 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0">
<div className="h-full px-3 py-4 overflow-y-auto bg-gray-50 dark:bg-gray-800">
<ul className="space-y-2 font-medium">
<li>
<a
href="#"
className="flex items-center p-2 text-gray-900 rounded-lg dark:text-white hover:bg-gray-100 dark:hover:bg-gray-700"
>
<span className="ms-3">Dashboard</span>
</a>
</li>
</ul>
</div>
</aside>
</>
);
};

View File

@ -1,61 +1,28 @@
"use client";
import { axiosInstance } from "@/app/lib/axios";
import moment from "moment";
import { useEffect, useState } from "react";
import { useRoomStore } from "@/app/stores/roomStore";
import { useEffect } from "react";
import { RoomList } from "./List";
import { Room } from "./Room";
export const RoomTable = () => {
const [rooms, setRooms] = useState<{
future: Room[];
actual: Room[];
past: Room[];
}>({
future: [],
actual: [],
past: [],
});
const { fetchRooms, actual, future, past } = useRoomStore();
useEffect(() => {
axiosInstance
.get<
{ id: string; name: string; createdAt: string }[]
>("/@me/class")
.then((classResponse) => {
if (classResponse.data.length)
axiosInstance
.get<
Room[]
>(`/@me/class/${classResponse.data[0].id}/rooms`)
.then((classes) => {
// Filter rooms by date, get future, actual and past rooms
const future = classes.data.filter((room) =>
moment(room.date).isAfter(moment(), "day"),
);
const actual = classes.data.filter((room) =>
moment(room.date).isSame(moment(), "day"),
);
const past = classes.data.filter((room) =>
moment(room.date).isBefore(moment(), "day"),
);
setRooms({ future, actual, past });
});
});
fetchRooms();
}, []);
return (
<div className="flex flex-col gap-4">
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Upcoming</h2>
<RoomList rooms={rooms.future} />
<RoomList rooms={future} />
</section>
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Today</h2>
<RoomList rooms={rooms.actual} />
<RoomList rooms={actual} />
</section>
<section className="flex flex-col gap-2">
<h2 className="font-semibold text-lg">Past</h2>
<RoomList rooms={rooms.past} />
<RoomList rooms={past} />
</section>
</div>
);

View File

@ -0,0 +1,42 @@
"use client";
import { FaRegUser } from "react-icons/fa";
import { ThemeSwitcher } from "../ThemeSwitcher/ThemeSwitcher";
import { SidebarItem } from "./item";
import { Link } from "@nextui-org/react";
import { useRouter } from "next/navigation";
export const Sidebar = () => {
const router = useRouter();
return (
<aside className="fixed top-0 left-0 w-64 h-screen transition-transform -translate-x-full sm:translate-x-0">
<div className="flex flex-col gap-2 h-full px-3 py-4 overflow-y-auto bg-foreground-100">
<ul className="font-medium gap-2">
<li>
<Link
className="flex justify-center p-2 text-gray-900 dark:text-white rounded-lg cursor-pointer"
onPress={() => router.push("/")}
>
Toogether
</Link>
</li>
</ul>
<section className="flex flex-col gap-2">
<ul>
<li className="hover:bg-foreground-300 rounded-md cursor-pointer">
<SidebarItem
href="/admin/users"
title="Users"
icon={<FaRegUser />}
/>
</li>
</ul>
</section>
<section className="mt-auto flex justify-between">
<ThemeSwitcher />
</section>
</div>
</aside>
);
};

View File

@ -0,0 +1,24 @@
"use client";
import { Link } from "@nextui-org/react";
import { useRouter } from "next/navigation";
export const SidebarItem = ({
title,
icon,
href,
}: {
title: string;
icon: React.ReactNode;
href: string;
}) => {
return (
<Link
onPress={() => useRouter().push(href)}
color="foreground"
className="w-full px-2 py-1 gap-2"
>
{icon}
{title}
</Link>
);
};

View File

@ -12,17 +12,15 @@ export const ThemeSwitcher = () => {
}, []);
return (
<div>
<Button
size="sm"
variant="flat"
className="min-w-0"
onPress={() =>
mounted && setTheme(theme === "light" ? "dark" : "light")
}
>
{mounted && theme === "light" ? "🌑" : "☀️"}
</Button>
</div>
<Button
size="sm"
variant="flat"
className="min-w-0"
onPress={() =>
mounted && setTheme(theme === "light" ? "dark" : "light")
}
>
{mounted && theme === "light" ? "🌑" : "☀️"}
</Button>
);
};

View File

@ -0,0 +1,19 @@
import { authOptions } from "@/authOptions";
import { getServerSession } from "next-auth";
import { Header } from "../components/Header";
export default async function Layout({
children,
}: {
children: React.ReactNode;
}) {
const session = await getServerSession(authOptions);
return (
<div>
<Header user={session?.user} />
<main>{children}</main>
</div>
);
}

View File

@ -0,0 +1,16 @@
import { Metadata } from "next";
import { RoomTable } from "../components/Room/Table";
export const metadata: Metadata = {
title: "Toogether | Home",
description:
"Toogether is a platform that allows you to create and join rooms to study together.",
};
export default async function HomePage() {
return (
<div className="flex flex-col gap-8 p-4">
<RoomTable />
</div>
);
}

View File

@ -0,0 +1,55 @@
import moment from "moment";
import { create } from "zustand";
import { Room } from "../components/Room/Room";
import { axiosInstance } from "../lib/axios";
type RoomStoreState = {
future: Room[];
actual: Room[];
past: Room[];
};
type RoomStoreActions = {
_setRooms: (rooms: Room[]) => void;
fetchRooms: () => void;
};
type RoomStore = RoomStoreState & RoomStoreActions;
const defaultState: RoomStoreState = {
future: [],
actual: [],
past: [],
};
export const useRoomStore = create<RoomStore>()((set) => ({
...defaultState,
_setRooms: (rooms) => {
const future = rooms.filter((room) =>
moment(room.date).isAfter(moment(), "day"),
);
const actual = rooms.filter((room) =>
moment(room.date).isSame(moment(), "day"),
);
const past = rooms.filter((room) =>
moment(room.date).isBefore(moment(), "day"),
);
set({ future, actual, past });
},
fetchRooms: () => {
axiosInstance
.get<
{ id: string; name: string; createdAt: string }[]
>("/@me/class")
.then((classResponse) => {
if (classResponse.data.length)
axiosInstance
.get<
Room[]
>(`/@me/class/${classResponse.data[0].id}/rooms`)
.then((classes) => {
useRoomStore.getState()._setRooms(classes.data);
});
});
},
}));

View File

@ -4491,6 +4491,11 @@ react-dom@19.0.0-rc-66855b96-20241106:
dependencies:
scheduler "0.25.0-rc-66855b96-20241106"
react-icons@^5.4.0:
version "5.4.0"
resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-5.4.0.tgz#443000f6e5123ee1b21ea8c0a716f6e7797f7416"
integrity sha512-7eltJxgVt7X64oHh6wSWNwwbKTCtMfK35hcjvJS0yxEAhPM8oUKdS3+kqaW1vicIltw+kR2unHaa12S9pPALoQ==
react-is@^16.13.1:
version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"