Skip to content
This repository was archived by the owner on Mar 19, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/app/(auth)/dashboard/certificate/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export default function Page() {
const totalPages = Math.ceil((certificates?.length || 0) / ITEMS_PER_PAGE);
const startIndex = (currentPage - 1) * ITEMS_PER_PAGE;
const currentCertificates = certificates?.slice(startIndex, startIndex + ITEMS_PER_PAGE) || [];
const placeholderCount = Math.max(0, ITEMS_PER_PAGE - currentCertificates.length);

if (loading) {
return <Loading message="Loading certificates..." />;
Expand Down Expand Up @@ -71,10 +72,12 @@ export default function Page() {

<div className='grid grid-cols-1 md:grid-cols-3 gap-6'>
{currentCertificates.map((certificate) => (
<CertificateCard
key={certificate._id}
certificate={certificate}
/>
<div key={certificate._id} className="min-h-[430px]">
<CertificateCard certificate={certificate} />
</div>
))}
{Array.from({ length: placeholderCount }).map((_, idx) => (
<div key={`ph-${idx}`} className="min-h-[420px]" aria-hidden />
))}
</div>
{/* Pagination Controls */}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/dashboard/manage-course/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ const CourseManagementPage: React.FC = () => {
<div className="min-h-screen">
<div className="max-w-7xl mx-auto">
{/* Title */}
<h1 className="text-3xl font-bold text-gray-900 mb-4">Published Course Management</h1>
<h1 className="text-3xl font-bold text-gray-900 mb-4">Courses Management</h1>
{/* Course Grid */}
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{currentCourses.map((course) => {
Expand Down
2 changes: 1 addition & 1 deletion src/app/(auth)/dashboard/manage-user/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ const ManageUserPage = () => {
{currentUsers.map((user) => (
<div
key={user._id}
className="flex items-center justify-between p-4 border rounded-lg hover:bg-gray-50 transition-colors"
className="flex items-center justify-between p-4 rounded-lg hover:bg-gray-50 transition-colors border border-gray-200"
>
<div className="flex items-center space-x-4">
<AvatarWithFallback
Expand Down
2 changes: 1 addition & 1 deletion src/components/common/ui/Card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ function Card({ className, ...props }: React.ComponentProps<'div'>) {
<div
data-slot="card"
className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm',
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl py-6 shadow-sm',
className
)}
{...props}
Expand Down
18 changes: 17 additions & 1 deletion src/components/dashboard/Sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import React from 'react';
import React, { useEffect, useState } from 'react';
import Image from 'next/image';
import Link from 'next/link';
import { usePathname } from 'next/navigation';
Expand Down Expand Up @@ -48,6 +48,9 @@ interface User {
}

const Sidebar = () => {
const [mounted, setMounted] = useState(false);
useEffect(() => setMounted(true), []);

const pathname = usePathname();
const { user } = useSelector((state: RootState) => state.auth) as { user: User | null };

Expand Down Expand Up @@ -102,6 +105,19 @@ const Sidebar = () => {
// Choose menu items based on user role
const menuItems: MenuItem[] = menuByRole[userRole] || menuByRole['user'];

// Render a deterministic skeleton on SSR to avoid hydration mismatch
if (!mounted) {
return (
<div className="w-full max-h-screen flex flex-col items-start p-4 rounded-2xl bg-white">
<nav className="flex flex-col gap-4 w-full">
<div className="h-9 rounded-lg bg-gray-100" />
<div className="h-9 rounded-lg bg-gray-100" />
<div className="h-9 rounded-lg bg-gray-100" />
</nav>
</div>
);
}

return (
<div className="w-full max-h-screen flex flex-col items-start p-4 rounded-2xl bg-white">
<nav className="flex flex-col gap-4 w-full">
Expand Down
7 changes: 4 additions & 3 deletions src/components/dashboard/certificate/CertificateCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ const CertificateCard: React.FC<CertificateCardProps> = ({ certificate }) => {

React.useEffect(() => {
const fetchUserAvatar = async () => {
if (!certificate?.user?._id) return;
try {
const userData = await getUserById(certificate.user._id);
const userData = await getUserById(certificate?.user?._id);
const user = userData.user || userData;
if (user?.avatar?.url) {
setUserAvatar(user.avatar.url);
Expand All @@ -26,7 +27,7 @@ const CertificateCard: React.FC<CertificateCardProps> = ({ certificate }) => {
};

fetchUserAvatar();
}, [certificate.user._id]);
}, [certificate?.user?._id]);

const formatDate = (dateString: string) => {
try {
Expand Down Expand Up @@ -67,7 +68,7 @@ const CertificateCard: React.FC<CertificateCardProps> = ({ certificate }) => {
<Image src="/assets/icons/menu.svg" alt="Menu" width={30} height={30} />
</div>
</div>
<div className="font-bold text-gray-800 text-xl my-3 leading-tight">
<div className="font-bold h-10 text-gray-800 text-xl my-3 leading-tight">
{certificate.courseName}
</div>
<div className="flex justify-between items-start text-xs text-gray-500">
Expand Down
3 changes: 2 additions & 1 deletion src/hooks/useCertificate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export const useCurrentUser = () => {
const data = await getCurrentUserAPI();
setUser(data);
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to fetch user');
// Không đăng nhập: coi như user null và KHÔNG coi là lỗi hiển thị
setUser(null);
} finally {
setLoading(false);
}
Expand Down
Loading