Skip to content
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
2 changes: 2 additions & 0 deletions FrontEnd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
"dayjs": "^1.11.13",
"face-api.js": "^0.22.2",
"firebase": "^11.9.1",
"html2canvas": "^1.4.1",
"jspdf": "^3.0.3",
"jwt-decode": "^4.0.0",
"lucide-react": "^0.513.0",
"react": "^19.1.0",
Expand Down
43 changes: 28 additions & 15 deletions FrontEnd/src/App.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import React, { useEffect } from "react";
import AppRouter from "./routes"; // Import AppRouter từ thư mục routes (routes/index.js)
import { ToastContainer } from "react-toastify"; // Chỉ import Container, không cần hàm toast nữa
import AppRouter from "./routes";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import "./App.css";
import { useDispatch, useSelector } from "react-redux";
import { fetchCurrentUser } from "./store/authSlice"; // Import action để lấy thông tin người dùng hiện tại

// === QUAN TRỌNG: Import component dịch vụ xử lý socket vừa tạo ===
// Đảm bảo đường dẫn này đúng với nơi bạn tạo file ở bước trước
import { fetchCurrentUser } from "./store/authSlice";
import NotificationSocketHandler from "./utils/NotificationSocketHandler";

function App() {
const dispatch = useDispatch();
const { isAuthenticated } = useSelector((state) => state.auth);

// 1. Giữ nguyên logic lấy thông tin user khi load trang
useEffect(() => {
if (isAuthenticated) {
dispatch(fetchCurrentUser());
Expand All @@ -23,14 +19,14 @@ function App() {

return (
<>
{/* Router điều hướng trang */}
<AppRouter />

{/* Container chứa Toast (Popup thông báo) */}
{/* Nó cần nằm ở root để hiển thị đè lên mọi thứ */}
{/* === 1. TOAST CŨ (Dành cho thông báo hệ thống chung) === */}
{/* Không set containerId -> Nó sẽ nhận các toast mặc định */}
<ToastContainer
position="bottom-left" // Vị trí giống Facebook
autoClose={5000}
enableMultiContainer
position="top-right"
autoClose={3000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
Expand All @@ -39,11 +35,28 @@ function App() {
draggable
pauseOnHover
theme="light"
icon={false}
/>

{/* === COMPONENT XỬ LÝ SOCKET === */}
{/* Chỉ render (và kết nối socket) khi user đã đăng nhập */}
{/* === 2. TOAST MỚI (Dành riêng cho thông báo Socket/Facebook) === */}
{/* Bọc trong div riêng như bạn muốn để dễ chỉnh CSS vị trí nếu cần */}
<div className="socket-toast-wrapper">
<ToastContainer
enableMultiContainer // Cho phép chạy song song với cái trên
containerId="socket-notification" // ĐẶT TÊN ĐỊNH DANH RIÊNG
position="bottom-left" // Vị trí góc dưới trái
autoClose={5000}
hideProgressBar={false}
newestOnTop={false}
closeOnClick
rtl={false}
pauseOnFocusLoss
draggable
pauseOnHover
theme="light"
icon={false} // Tắt icon mặc định để dùng giao diện custom của bạn
/>
</div>

{isAuthenticated && <NotificationSocketHandler />}
</>
);
Expand Down
37 changes: 37 additions & 0 deletions FrontEnd/src/assets/PurchaseHistory/PurchaseHistory.css
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,43 @@
background-color: #e9ecef;
}

.flearning-qr-container {
display: flex;
flex-direction: column;
align-items: center;
background-color: #fff;
padding: 15px;
border-radius: 12px;
border: 1px solid #eee;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
}

.flearning-qr-wrapper {
width: 100%;
max-width: 280px; /* Giới hạn chiều rộng QR */
border-radius: 8px;
overflow: hidden;
border: 1px solid #e0e0e0;
background: #fff;
}

.flearning-qr-image {
width: 100%;
height: auto;
display: block;
object-fit: contain;
}

.flearning-qr-note {
font-size: 13px;
color: #666;
text-align: center;
margin-top: 15px;
margin-bottom: 0;
line-height: 1.5;
font-style: italic;
}

@media (max-width: 768px) {
.flearning-purchase-details-grid {
grid-template-columns: 1fr;
Expand Down
48 changes: 24 additions & 24 deletions FrontEnd/src/assets/Toast/CustomToast.css
Original file line number Diff line number Diff line change
Expand Up @@ -246,11 +246,11 @@
}

/* ============================================================
Toast (React-Toastify)
Toast (Socket / Global Notification)
============================================================ */

/* Toastify overrides */
.Toastify__toast {
/* Toastify overrides - Áp dụng cho wrapper mới */
.socket-toast-wrapper .Toastify__toast {
border-radius: var(--fb-radius) !important;
box-shadow: var(--fb-shadow) !important;
background: var(--fb-bg) !important;
Expand All @@ -265,19 +265,19 @@
overflow: visible !important;
}

.Toastify__toast-body {
.socket-toast-wrapper .Toastify__toast-body {
padding: 0 !important;
margin: 0 !important;
width: 100%;
display: flex;
align-items: flex-start;
}

.Toastify__close-button {
.socket-toast-wrapper .Toastify__close-button {
display: none !important;
}

.Toastify__progress-bar {
.socket-toast-wrapper .Toastify__progress-bar {
height: 3px !important;
bottom: 0 !important;
background-color: var(--fb-primary) !important;
Expand All @@ -287,7 +287,7 @@
}

/* Custom toast wrapper */
.fb-toast-wrapper {
.socket-toast-wrapper .fb-toast-wrapper {
width: 100%;
background: var(--fb-bg);
padding: 12px 16px;
Expand All @@ -298,18 +298,18 @@
position: relative;
}

.toast-header {
.socket-toast-wrapper .toast-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 4px;
}
.toast-title {
.socket-toast-wrapper .toast-title {
font-size: 15px;
font-weight: 600;
color: var(--fb-muted);
}
.toast-close-btn {
.socket-toast-wrapper .toast-close-btn {
background: transparent;
border: none;
border-radius: 50%;
Expand All @@ -323,32 +323,32 @@
transition: background-color var(--fb-transition);
padding: 0;
}
.toast-close-btn:hover {
.socket-toast-wrapper .toast-close-btn:hover {
background-color: #e4e6eb;
color: var(--fb-text);
}

.toast-content-wrapper {
.socket-toast-wrapper .toast-content-wrapper {
display: flex;
align-items: center;
gap: 12px;
position: relative;
}
.toast-avatar-container {
.socket-toast-wrapper .toast-avatar-container {
position: relative;
width: 56px;
height: 56px;
flex-shrink: 0;
}
.toast-avatar {
.socket-toast-wrapper .toast-avatar {
width: 100%;
height: 100%;
border-radius: 50%;
object-fit: cover;
border: 1px solid rgba(0, 0, 0, 0.1);
}

.toast-action-icon {
.socket-toast-wrapper .toast-action-icon {
position: absolute;
bottom: -2px;
right: -2px;
Expand All @@ -362,29 +362,29 @@
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
z-index: 2;
}
.toast-action-icon.like {
.socket-toast-wrapper .toast-action-icon.like {
background-color: var(--fb-danger);
}
.toast-action-icon.comment {
.socket-toast-wrapper .toast-action-icon.comment {
background-color: var(--fb-success);
}
.toast-action-icon.system {
.socket-toast-wrapper .toast-action-icon.system {
background-color: var(--fb-primary);
}
.toast-action-icon svg {
.socket-toast-wrapper .toast-action-icon svg {
width: 12px;
height: 12px;
color: #fff;
fill: #fff;
}

.toast-text-content {
.socket-toast-wrapper .toast-text-content {
display: flex;
flex-direction: column;
flex-grow: 1;
padding-right: 12px;
}
.toast-message {
.socket-toast-wrapper .toast-message {
margin: 0;
font-size: 15px;
line-height: 1.3333;
Expand All @@ -395,17 +395,17 @@
-webkit-box-orient: vertical;
overflow: hidden;
}
.toast-message strong {
.socket-toast-wrapper .toast-message strong {
font-weight: 600;
color: var(--fb-text);
}
.toast-time {
.socket-toast-wrapper .toast-time {
font-size: 13px;
color: var(--fb-primary);
font-weight: 500;
margin-top: 4px;
}
.toast-read-dot {
.socket-toast-wrapper .toast-read-dot {
width: 10px;
height: 10px;
background-color: var(--fb-primary);
Expand Down
Loading