From a7c3aecc6eb654fc3a6627543fd76c11695ae93c Mon Sep 17 00:00:00 2001 From: AzenKain Date: Thu, 14 May 2026 11:40:37 +0700 Subject: [PATCH] feat: implement authentication modules and add daily statistics dashboard component --- src/app/(admin)/layout.tsx | 69 ++++++++++++++++--- .../{(auth) => auth}/layout.tsx | 4 -- .../{(auth) => auth}/reset-password/page.tsx | 2 +- .../{(auth) => auth}/signin/page.tsx | 0 .../{(auth) => auth}/signup/page.tsx | 0 src/components/auth/SignInForm.tsx | 7 +- src/components/auth/SignUpForm.tsx | 8 +-- src/components/ecommerce/DailyNewChart.tsx | 4 +- src/config/config.ts | 2 +- 9 files changed, 70 insertions(+), 26 deletions(-) rename src/app/(full-width-pages)/{(auth) => auth}/layout.tsx (92%) rename src/app/(full-width-pages)/{(auth) => auth}/reset-password/page.tsx (99%) rename src/app/(full-width-pages)/{(auth) => auth}/signin/page.tsx (100%) rename src/app/(full-width-pages)/{(auth) => auth}/signup/page.tsx (100%) diff --git a/src/app/(admin)/layout.tsx b/src/app/(admin)/layout.tsx index 362a432..26b844d 100644 --- a/src/app/(admin)/layout.tsx +++ b/src/app/(admin)/layout.tsx @@ -1,13 +1,17 @@ "use client"; import { useSidebar } from "@/context/SidebarContext"; +import { UserData } from "@/interface/user"; import AppHeader from "@/layout/AppHeader"; import AppSidebar from "@/layout/AppSidebar"; import Backdrop from "@/layout/Backdrop"; import { apiGetCurrentUser } from "@/service/auth"; import { setUserData } from "@/store/features/userSlice"; -import React, { useEffect } from "react"; -import { useDispatch } from "react-redux"; +import { RootState } from "@/store/store"; +import { useRouter } from "next/navigation"; +import React, { useEffect, useState } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { toast } from "sonner"; export default function AdminLayout({ children, @@ -15,20 +19,44 @@ export default function AdminLayout({ children: React.ReactNode; }) { const { isExpanded, isHovered, isMobileOpen } = useSidebar(); - const dispatch = useDispatch() + const dispatch = useDispatch(); + const router = useRouter(); + const userData = useSelector((state: RootState) => state.user.data); + const [isLoading, setIsLoading] = useState(!userData); useEffect(() => { + if (userData) { + setIsLoading(false); + return; + } + const fetchUser = async () => { try { - const userData = await apiGetCurrentUser(); - dispatch(setUserData(userData.data)); - } catch (err) { - console.error("Lỗi:", err); + setIsLoading(true); + const res = await apiGetCurrentUser(); + const userData: UserData = res; + + const allowedRoles = ["ADMIN", "MOD", "HISTORIAN"]; + + const isBanned = userData.roles.some((role) => role.name === "BANNED"); + + const hasPermission = userData.roles.some((role) => + allowedRoles.includes(role.name) + ); + + if (isBanned || !hasPermission) { + toast.error("Bạn không có quyền truy cập"); + router.replace("/auth/signin"); + } + dispatch(setUserData(res.data)); + } catch { + router.replace("/auth/signin"); + } finally { + setIsLoading(false); } }; fetchUser(); - }, []) - + }, [dispatch, userData, router]); // Dynamic class for main content margin based on sidebar state const mainContentMargin = isMobileOpen @@ -37,6 +65,29 @@ export default function AdminLayout({ ? "lg:ml-[290px]" : "lg:ml-[90px]"; + if (isLoading || !userData) { + return ( +
+
+ {/* Spinner */} +
+
+
+
+ {/* Text */} +
+

+ Đang tải dữ liệu +

+

+ Vui lòng chờ trong giây lát... +

+
+
+
+ ); + } + return (
{/* Sidebar and Backdrop */} diff --git a/src/app/(full-width-pages)/(auth)/layout.tsx b/src/app/(full-width-pages)/auth/layout.tsx similarity index 92% rename from src/app/(full-width-pages)/(auth)/layout.tsx rename to src/app/(full-width-pages)/auth/layout.tsx index cb25ed0..65b73ad 100644 --- a/src/app/(full-width-pages)/(auth)/layout.tsx +++ b/src/app/(full-width-pages)/auth/layout.tsx @@ -1,9 +1,5 @@ -import GridShape from "@/components/common/GridShape"; import ThemeTogglerTwo from "@/components/common/ThemeTogglerTwo"; - import { ThemeProvider } from "@/context/ThemeContext"; -import Image from "next/image"; -import Link from "next/link"; import React from "react"; export default function AuthLayout({ diff --git a/src/app/(full-width-pages)/(auth)/reset-password/page.tsx b/src/app/(full-width-pages)/auth/reset-password/page.tsx similarity index 99% rename from src/app/(full-width-pages)/(auth)/reset-password/page.tsx rename to src/app/(full-width-pages)/auth/reset-password/page.tsx index edd8c89..fe57643 100644 --- a/src/app/(full-width-pages)/(auth)/reset-password/page.tsx +++ b/src/app/(full-width-pages)/auth/reset-password/page.tsx @@ -100,7 +100,7 @@ export default function ResetPasswordForm() {
diff --git a/src/app/(full-width-pages)/(auth)/signin/page.tsx b/src/app/(full-width-pages)/auth/signin/page.tsx similarity index 100% rename from src/app/(full-width-pages)/(auth)/signin/page.tsx rename to src/app/(full-width-pages)/auth/signin/page.tsx diff --git a/src/app/(full-width-pages)/(auth)/signup/page.tsx b/src/app/(full-width-pages)/auth/signup/page.tsx similarity index 100% rename from src/app/(full-width-pages)/(auth)/signup/page.tsx rename to src/app/(full-width-pages)/auth/signup/page.tsx diff --git a/src/components/auth/SignInForm.tsx b/src/components/auth/SignInForm.tsx index b418e81..496fc3b 100644 --- a/src/components/auth/SignInForm.tsx +++ b/src/components/auth/SignInForm.tsx @@ -71,7 +71,6 @@ export default function SignInForm() { toast.success("Đăng nhập thành công!"); const data = await apiGetCurrentUser(); - // console.log("Current User Data:", data); if (data?.data) { dispatch(setUserData(data.data)); router.push("/"); @@ -79,7 +78,7 @@ export default function SignInForm() { } else { toast.error("Email hoặc mật khẩu không đúng."); } - } catch (error) { + } catch { setErrorMsg("Lỗi khi đăng nhập. Vui lòng thử lại."); toast.error("Đăng nhập thất bại. Vui lòng kiểm tra lại thông tin."); } finally { @@ -206,7 +205,7 @@ export default function SignInForm() {
Forgot password? @@ -256,7 +255,7 @@ export default function SignInForm() {

Don't have an account? {""} Sign Up diff --git a/src/components/auth/SignUpForm.tsx b/src/components/auth/SignUpForm.tsx index d440f70..a4949f9 100644 --- a/src/components/auth/SignUpForm.tsx +++ b/src/components/auth/SignUpForm.tsx @@ -72,7 +72,7 @@ export default function SignUpForm() { setLoading(true); await apiCreateOTP(formData.email); setStep(2); - } catch (error) { + } catch { setErrorMsg("Lỗi khi tạo OTP. Vui lòng thử lại."); toast.error("Tạo OTP thất bại. Vui lòng kiểm tra lại thông tin."); } finally { @@ -106,7 +106,7 @@ export default function SignUpForm() { token_id: tokenId, }; - const signupRes = await apiSignUp(signupPayload); + await apiSignUp(signupPayload); await Swal.fire({ title: "Tạo tài khoản thành công!. Quay về trang đăng nhập để tiếp tục", @@ -131,7 +131,7 @@ export default function SignUpForm() {

@@ -332,7 +332,7 @@ export default function SignUpForm() {

Already have an account?{" "} Sign In diff --git a/src/components/ecommerce/DailyNewChart.tsx b/src/components/ecommerce/DailyNewChart.tsx index f00d123..6b63b48 100644 --- a/src/components/ecommerce/DailyNewChart.tsx +++ b/src/components/ecommerce/DailyNewChart.tsx @@ -31,7 +31,6 @@ export default function MonthlyNewChart() { try { const response = await getStatistics({ start_date: start, end_date: end }); if (response?.data) { - // Tổng hợp theo tháng const monthsMap: { [key: string]: number } = {}; response.data.forEach(item => { @@ -56,8 +55,7 @@ export default function MonthlyNewChart() { setChartData(aggregated); } - } catch (error) { - console.error("Failed to fetch chart data", error); + } catch { } finally { setLoading(false); } diff --git a/src/config/config.ts b/src/config/config.ts index 43f6edc..61fff74 100644 --- a/src/config/config.ts +++ b/src/config/config.ts @@ -67,7 +67,7 @@ api.interceptors.response.use( isRefreshing = true; try { - await axios.post(`${baseURL}/auth/refresh`, {}, { withCredentials: true }); + await axios.post(`${baseURL}/auth/refresh`, undefined, { withCredentials: true }); processQueue(null);