Skip to content

Refactor admin page#2

Merged
dev3h merged 4 commits intomasterfrom
feature/refactor-admin-page
Dec 9, 2025
Merged

Refactor admin page#2
dev3h merged 4 commits intomasterfrom
feature/refactor-admin-page

Conversation

@dev3h
Copy link
Owner

@dev3h dev3h commented Dec 9, 2025

No description provided.

@vercel
Copy link

vercel bot commented Dec 9, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Comments Updated (UTC)
news-reactjs-web Ready Ready Preview Comment Dec 9, 2025 4:12am

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR implements a comprehensive refactor of the admin page, introducing modern UI components, improved authentication flows, and a new post editor experience.

Key Changes:

  • Replaced CKEditor with BlockNote editor for enhanced content editing
  • Implemented dashboards for both admin and author roles with charts and statistics
  • Added JWT refresh token mechanism with queue-based request handling
  • Enhanced admin authentication UI with modern animations and styling

Reviewed changes

Copilot reviewed 39 out of 41 changed files in this pull request and generated 13 comments.

Show a summary per file
File Description
src/services/authServices/AdminAuthServices.js Refactored to use centralized API endpoints and added refresh token method
src/configs/axiosConfig.js Completely rewritten to support JWT refresh token logic with request queuing
src/configs/axiosUserConfig.js New file separating user-specific axios configuration
src/pages/auth-pages/admin-auth/Login.jsx Redesigned with modern UI, enhanced validation, and role-based routing
src/pages/app-pages/admin-pages/Dashboard.jsx New admin dashboard with statistics cards and chart visualizations
src/pages/app-pages/author-pages/Dashboard.jsx New author dashboard with performance metrics and recent posts
src/pages/app-pages/admin-pages/post/Create.jsx Integrated BlockNote editor and added live preview functionality
src/pages/app-pages/admin-pages/post/Edit.jsx Integrated BlockNote editor with live preview and improved UX
src/components/BlockNoteEditor/index.jsx New rich text editor component with fullscreen mode and HTML conversion
src/components/PostPreview/index.jsx New component for real-time post preview during editing
src/layouts/auth-layouts/admin-layouts/index.jsx Redesigned with gradient backgrounds and modern card styling
src/layouts/app-layouts/admin-layouts/SideBar.jsx Enhanced navigation with proper URL synchronization and state management
src/constants/routeConstants.js New centralized route constants for better maintainability
src/configs/apiEndpoints.js Centralized API endpoint definitions
src/validation/auth/adminAuthValidation.js Comprehensive validation rules for admin authentication
src/styles/admin-auth.css New CSS animations for login page
src/services/adminServices/dashboardServices.js Service layer for dashboard data with date formatting utilities
src/components/Dashboard/StatCard.jsx Reusable statistics card component with gradient support
src/components/Dashboard/ChartCard.jsx Reusable chart wrapper component
package.json Added BlockNote, Chart.js, and styled-components dependencies

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +43 to +45
onChange: () => {
handleEditorChange();
},
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The editor change handler is called directly in the onChange callback, which triggers on every keystroke. This approach bypasses the debouncing logic defined in the useEffect hook (lines 85-112). Consider removing the direct call here and relying solely on the debounced subscription to avoid performance issues with rapid state updates.

Suggested change
onChange: () => {
handleEditorChange();
},
// Removed direct onChange handler to rely on debounced logic in useEffect

Copilot uses AI. Check for mistakes.

try {
const blocks = editor.document;
const html =editor.blocksToHTMLLossy(blocks);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The typo in line 72: editor.blocksToHTMLLossy is missing a space after html =. It should be const html = editor.blocksToHTMLLossy(blocks);

Suggested change
const html =editor.blocksToHTMLLossy(blocks);
const html = editor.blocksToHTMLLossy(blocks);

Copilot uses AI. Check for mistakes.
if (timeoutId) {
clearTimeout(timeoutId);
}
if (unsubscribe) {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The unsubscribe function might not exist if editor.onChange doesn't return an unsubscribe function. Consider checking if unsubscribe is a function before calling it: if (typeof unsubscribe === 'function') { unsubscribe(); }

Suggested change
if (unsubscribe) {
if (typeof unsubscribe === 'function') {

Copilot uses AI. Check for mistakes.
Comment on lines +79 to +112
setRecentPosts([
{
id: 1,
title: "Hướng dẫn sử dụng React Hooks hiệu quả",
status: "published",
views: 1250,
created_at: "2025-10-20T10:30:00Z",
category: "Lập trình"
},
{
id: 2,
title: "10 mẹo tối ưu hóa hiệu suất website",
status: "draft",
views: 0,
created_at: "2025-10-19T15:45:00Z",
category: "Web Development"
},
{
id: 3,
title: "Xu hướng công nghệ 2025",
status: "published",
views: 890,
created_at: "2025-10-18T09:20:00Z",
category: "Công nghệ"
},
{
id: 4,
title: "Machine Learning cơ bản cho người mới",
status: "published",
views: 2100,
created_at: "2025-10-17T14:10:00Z",
category: "AI/ML"
}
]);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded date "2025-10-20T10:30:00Z" and similar dates in this mock data are in the past relative to the current date (December 9, 2025). This might cause confusion when displaying "recent" posts. Consider using dynamic dates relative to the current date, like new Date().toISOString() or calculating dates relative to today.

Copilot uses AI. Check for mistakes.
try {
const stored = localStorage.getItem("admin");
return stored ? JSON.parse(stored) : null;
} catch (e) {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error handling for JSON parsing uses a try-catch block which is good, but it returns null on error without logging or notifying the user. This could lead to silent failures when localStorage contains corrupted data. Consider adding error logging: console.error('Failed to parse admin data from localStorage:', e);

Suggested change
} catch (e) {
} catch (e) {
console.error('Failed to parse admin data from localStorage:', e);

Copilot uses AI. Check for mistakes.
@@ -0,0 +1,60 @@
import React from 'react';
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The component imports React but never uses it. With modern React (17+), this import is unnecessary. Consider removing it.

Suggested change
import React from 'react';

Copilot uses AI. Check for mistakes.
return tag?.label || id;
});
};

Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The variable name editorError has been removed but editorContent state is still being used. However, the validation is now handled by the form rules. Consider clarifying the comment or documentation to indicate that content validation is now managed by the Form component's validation system rather than custom state.

Suggested change
// Note: Content validation is now managed by the Form component's validation system,
// not by custom state or error variables. The editorContent state is used for preview and data handling.

Copilot uses AI. Check for mistakes.
// title: () => <ColumnSort type="id" title="ID" handleSort={handleSort} />,
dataIndex: "id",
key: "id",
width: 200,
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] The width property is set to a fixed pixel value (200px) which may not work well on smaller screens or with responsive layouts. Consider using a percentage-based width or making it configurable, or testing this value across different viewport sizes to ensure it doesn't cause layout issues.

Copilot uses AI. Check for mistakes.
});
} else {
if (error?.response?.status === 401) {
if (error?.response?.data?.cause === 'JsonWebTokenError') {
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: When JWT token errors occur (line 58-61), the code removes the admin token and reloads the page without any user notification or proper logout flow. This abrupt behavior could be confusing for users. Consider showing a notification message before reload: notification.warning({ message: 'Phiên đăng nhập hết hạn', description: 'Vui lòng đăng nhập lại' }); before window.location.reload();

Suggested change
if (error?.response?.data?.cause === 'JsonWebTokenError') {
if (error?.response?.data?.cause === 'JsonWebTokenError') {
notification.warning({
message: 'Phiên đăng nhập hết hạn',
description: 'Vui lòng đăng nhập lại'
});

Copilot uses AI. Check for mistakes.
@@ -1,11 +1,11 @@
import adminAuthServices from "@/services/authServices/adminAuthServices";
import AdminAuthServices from "@/services/authServices/AdminAuthServices";
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unused import AdminAuthServices.

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 39 out of 41 changed files in this pull request and generated 16 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}
});

const contextValue = useMemo(() => ({ admin, setAdmin }), [admin, setAdmin]);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useMemo dependency array includes setAdmin, but setAdmin from context/state setters should be stable and doesn't need to be in the dependency array. Including it is unnecessary and could cause extra re-renders if the reference changes.

Suggested change
const contextValue = useMemo(() => ({ admin, setAdmin }), [admin, setAdmin]);
const contextValue = useMemo(() => ({ admin, setAdmin }), [admin]);

Copilot uses AI. Check for mistakes.
Comment on lines 15 to +24
useEffect(() => {
clearAuthData();
}, []);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useEffect dependency array is missing setAdmin and navigate. While these are typically stable references, it's better to include all dependencies or use ESLint disable comments with justification to avoid potential issues.

Copilot uses AI. Check for mistakes.
processQueue(refreshError, null);
failedQueue = []; // Clear queue on error
localStorage.removeItem("admin");
globalThis.location.reload();
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using globalThis.location.reload() forces a full page reload. Consider navigating to the login page instead to preserve the SPA experience and avoid losing application state unnecessarily.

Copilot uses AI. Check for mistakes.
Comment on lines +102 to +118
try {
const response = await AdminAuthServices.refreshToken();
const token = response?.accessToken;

if (token) {
const authData = { token };
const authString = JSON.stringify(authData);
localStorage.setItem("admin", authString);

// Cập nhật header cho request hiện tại
originalRequest.headers.Authorization = `Bearer ${token}`;

// Xử lý tất cả requests đang chờ
processQueue(null, token);

// Retry request ban đầu
return axiosInstance(originalRequest);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security concern: The token refresh logic could be exploited if an attacker can force 401 errors. The code doesn't validate the refresh token response before storing it. Add validation to ensure the response contains a valid token structure before saving to localStorage.

Copilot uses AI. Check for mistakes.
</Form.Item>
</Flex>
<UploadPhotoInput propUpload={propUpload} />
<ButtonAddForm loading={loading} />
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing PropTypes validation for propUpload prop. This component receives a propUpload prop but doesn't validate it, which could lead to runtime errors if incorrect data is passed.

Copilot uses AI. Check for mistakes.
thumbnail: response?.photo || null
});
} catch (error) {
console.log(error);
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty catch block silently swallows errors. At minimum, log the error to help with debugging. Consider also showing a user-friendly error message.

Suggested change
console.log(error);
console.log(error);
message.error("Không thể tải dữ liệu bài viết. Vui lòng thử lại sau.");

Copilot uses AI. Check for mistakes.
Comment on lines 118 to 129
const getOpenKeysFromSelectedKey = (key) => {
if (!key) return [];

// Tìm parent key từ selected key
// Ví dụ: "2-1" -> parent là "2"
const parentKey = key.split('-')[0];

// Chỉ return parent key nếu selected key có dấu gạch ngang (là submenu)
if (key.includes('-')) {
return [parentKey];
}
return [];
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function getOpenKeysFromSelectedKey assumes a specific key format ("2-1") but doesn't validate the input. If the key format changes or is malformed, this could lead to unexpected behavior. Consider adding validation or documentation about the expected key format.

Suggested change
const getOpenKeysFromSelectedKey = (key) => {
if (!key) return [];
// Tìm parent key từ selected key
// Ví dụ: "2-1" -> parent là "2"
const parentKey = key.split('-')[0];
// Chỉ return parent key nếu selected key có dấu gạch ngang (là submenu)
if (key.includes('-')) {
return [parentKey];
}
return [];
// Expected key format: "parent-child" (e.g., "2-1"). Returns [parent] if valid, [] otherwise.
const getOpenKeysFromSelectedKey = (key) => {
if (!key) return [];
// Validate key format: must be two non-empty segments separated by a single dash
const keyParts = key.split('-');
if (keyParts.length !== 2 || !keyParts[0] || !keyParts[1]) {
return [];
}
// Return parent key if valid
return [keyParts[0]];

Copilot uses AI. Check for mistakes.
localStorage.removeItem("admin");
setAdmin(null);
navigate("/auth/admin/login");
}
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing semicolon at the end of the statement. While JavaScript allows this, it's inconsistent with the coding style in other parts of the file.

Copilot uses AI. Check for mistakes.
message: 'Phiên đăng nhập hết hạn',
description: 'Vui lòng đăng nhập lại'
});
globalThis.location.reload();
Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using globalThis.location.reload() forces a full page reload which will lose any unsaved state. Consider navigating to the login page instead using navigate('/auth/admin/login') to preserve the SPA experience.

Copilot uses AI. Check for mistakes.
Comment on lines +85 to +113
useEffect(() => {
if (!editor) return;

let timeoutId = null;

// Debounce the change handler to avoid rapid successive calls
const debouncedHandler = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}

timeoutId = setTimeout(() => {
handleEditorChange();
}, 100);
};

// Subscribe to editor changes
const unsubscribe = editor.onChange(debouncedHandler);

return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (typeof unsubscribe === 'function') {
unsubscribe();
}
};
}, [editor, handleEditorChange]);

Copy link

Copilot AI Dec 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Duplicate change handler subscription. The editor's onChange is already set in useCreateBlockNote (line 43), but then another subscription is created in the useEffect (line 102). This will cause the change handler to fire twice for every edit, which is inefficient and could lead to performance issues or unexpected behavior.

Suggested change
useEffect(() => {
if (!editor) return;
let timeoutId = null;
// Debounce the change handler to avoid rapid successive calls
const debouncedHandler = () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
handleEditorChange();
}, 100);
};
// Subscribe to editor changes
const unsubscribe = editor.onChange(debouncedHandler);
return () => {
if (timeoutId) {
clearTimeout(timeoutId);
}
if (typeof unsubscribe === 'function') {
unsubscribe();
}
};
}, [editor, handleEditorChange]);
// Removed duplicate editor.onChange subscription useEffect.

Copilot uses AI. Check for mistakes.
@dev3h dev3h force-pushed the feature/refactor-admin-page branch from 7ce6d53 to 8fdfe70 Compare December 9, 2025 04:12
@dev3h dev3h merged commit 701e3f4 into master Dec 9, 2025
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants