Spaces:
Sleeping
Sleeping
Commit ·
12a94f6
1
Parent(s): b4bf04d
sửa giao diện dark mode cho sáng hơn chút
Browse files- src/components/roomlist/DMList.jsx +7 -1
- src/hooks/useDMList.js +6 -1
- src/services/dm.service.js +8 -1
- src/store/slices/dmSlice.js +6 -1
- src/styles/theme.css +20 -20
src/components/roomlist/DMList.jsx
CHANGED
|
@@ -1,4 +1,4 @@
|
|
| 1 |
-
import { useState } from "react";
|
| 2 |
import { useSelector, useDispatch } from "react-redux";
|
| 3 |
import { FiMessageSquare, FiUserPlus, FiSearch } from "react-icons/fi";
|
| 4 |
import { setActiveRoom } from "../../store/slices/appSlice";
|
|
@@ -211,8 +211,14 @@ function DMList({ activeRoom, setActiveRoom: setActiveRoomProp }) {
|
|
| 211 |
const dispatch = useDispatch();
|
| 212 |
const { isDark } = useSelector((state) => state.theme);
|
| 213 |
const { loading: dmLoading, conversations, conversationsFetched } = useSelector((state) => state.dm);
|
|
|
|
| 214 |
const [sentRequests, setSentRequests] = useState([]);
|
| 215 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 216 |
const {
|
| 217 |
items,
|
| 218 |
onlineStatus,
|
|
|
|
| 1 |
+
import { useState, useEffect } from "react";
|
| 2 |
import { useSelector, useDispatch } from "react-redux";
|
| 3 |
import { FiMessageSquare, FiUserPlus, FiSearch } from "react-icons/fi";
|
| 4 |
import { setActiveRoom } from "../../store/slices/appSlice";
|
|
|
|
| 211 |
const dispatch = useDispatch();
|
| 212 |
const { isDark } = useSelector((state) => state.theme);
|
| 213 |
const { loading: dmLoading, conversations, conversationsFetched } = useSelector((state) => state.dm);
|
| 214 |
+
console.log("[DMList] Render, conversations:", conversations.length, "fetched:", conversationsFetched);
|
| 215 |
const [sentRequests, setSentRequests] = useState([]);
|
| 216 |
|
| 217 |
+
useEffect(() => {
|
| 218 |
+
console.log("[DMList] MOUNTED");
|
| 219 |
+
return () => console.log("[DMList] UNMOUNTED");
|
| 220 |
+
}, []);
|
| 221 |
+
|
| 222 |
const {
|
| 223 |
items,
|
| 224 |
onlineStatus,
|
src/hooks/useDMList.js
CHANGED
|
@@ -43,6 +43,7 @@ function matchesStudyBot(query) {
|
|
| 43 |
export function useDMList() {
|
| 44 |
const dispatch = useDispatch();
|
| 45 |
const { conversations, onlineUsers, conversationsFetched } = useSelector((state) => state.dm);
|
|
|
|
| 46 |
|
| 47 |
const [searchResults, setSearchResults] = useState([]);
|
| 48 |
const [searchQuery, setSearchQuery] = useState("");
|
|
@@ -54,10 +55,14 @@ export function useDMList() {
|
|
| 54 |
const processedMessageIds = useRef(new Set());
|
| 55 |
|
| 56 |
// Fetch conversations on mount — only if not already fetched
|
|
|
|
| 57 |
useEffect(() => {
|
| 58 |
if (conversationsFetched) return; // Skip: already cached
|
|
|
|
|
|
|
| 59 |
dispatch(fetchConversations());
|
| 60 |
-
|
|
|
|
| 61 |
|
| 62 |
// Listen to realtime updates via WebSocket
|
| 63 |
useEffect(() => {
|
|
|
|
| 43 |
export function useDMList() {
|
| 44 |
const dispatch = useDispatch();
|
| 45 |
const { conversations, onlineUsers, conversationsFetched } = useSelector((state) => state.dm);
|
| 46 |
+
console.log("[useDMList] Hook called, conversations:", conversations.length, "fetched:", conversationsFetched);
|
| 47 |
|
| 48 |
const [searchResults, setSearchResults] = useState([]);
|
| 49 |
const [searchQuery, setSearchQuery] = useState("");
|
|
|
|
| 55 |
const processedMessageIds = useRef(new Set());
|
| 56 |
|
| 57 |
// Fetch conversations on mount — only if not already fetched
|
| 58 |
+
const isFetchingRef = useRef(false);
|
| 59 |
useEffect(() => {
|
| 60 |
if (conversationsFetched) return; // Skip: already cached
|
| 61 |
+
if (isFetchingRef.current) return; // Prevent duplicate requests
|
| 62 |
+
isFetchingRef.current = true;
|
| 63 |
dispatch(fetchConversations());
|
| 64 |
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
| 65 |
+
}, [dispatch]);
|
| 66 |
|
| 67 |
// Listen to realtime updates via WebSocket
|
| 68 |
useEffect(() => {
|
src/services/dm.service.js
CHANGED
|
@@ -7,7 +7,14 @@ export const dmService = {
|
|
| 7 |
createOrGetConversation: (userId) => api.post("/dms", { userId }),
|
| 8 |
|
| 9 |
/** Get list of conversations */
|
| 10 |
-
getConversations: (params) =>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 11 |
|
| 12 |
// === Messages ===
|
| 13 |
|
|
|
|
| 7 |
createOrGetConversation: (userId) => api.post("/dms", { userId }),
|
| 8 |
|
| 9 |
/** Get list of conversations */
|
| 10 |
+
getConversations: async (params) => {
|
| 11 |
+
const response = await api.get("/dms", { params });
|
| 12 |
+
console.log("[dmService.getConversations] API response:", {
|
| 13 |
+
count: response.data?.data?.length || response.data?.conversations?.length || response.data?.length || 0,
|
| 14 |
+
data: response.data,
|
| 15 |
+
});
|
| 16 |
+
return response;
|
| 17 |
+
},
|
| 18 |
|
| 19 |
// === Messages ===
|
| 20 |
|
src/store/slices/dmSlice.js
CHANGED
|
@@ -8,9 +8,13 @@ export const fetchConversations = createAsyncThunk(
|
|
| 8 |
"dm/fetchConversations",
|
| 9 |
async (_, { rejectWithValue }) => {
|
| 10 |
try {
|
|
|
|
| 11 |
const { data } = await dmService.getConversations({ page: 1, limit: 20 });
|
| 12 |
-
|
|
|
|
|
|
|
| 13 |
} catch (err) {
|
|
|
|
| 14 |
return rejectWithValue(err.response?.data?.message || "Không thể tải danh sách trò chuyện");
|
| 15 |
}
|
| 16 |
}
|
|
@@ -243,6 +247,7 @@ const dmSlice = createSlice({
|
|
| 243 |
.addCase(fetchConversations.fulfilled, (state, action) => {
|
| 244 |
state.loading = false;
|
| 245 |
state.conversations = action.payload;
|
|
|
|
| 246 |
})
|
| 247 |
.addCase(fetchConversations.rejected, (state, action) => {
|
| 248 |
state.loading = false;
|
|
|
|
| 8 |
"dm/fetchConversations",
|
| 9 |
async (_, { rejectWithValue }) => {
|
| 10 |
try {
|
| 11 |
+
console.log("[fetchConversations] Calling API...");
|
| 12 |
const { data } = await dmService.getConversations({ page: 1, limit: 20 });
|
| 13 |
+
const result = data.data || data.conversations || data || [];
|
| 14 |
+
console.log("[fetchConversations] Result:", { count: result.length, ids: result.map(c => c.id) });
|
| 15 |
+
return result;
|
| 16 |
} catch (err) {
|
| 17 |
+
console.error("[fetchConversations] Error:", err);
|
| 18 |
return rejectWithValue(err.response?.data?.message || "Không thể tải danh sách trò chuyện");
|
| 19 |
}
|
| 20 |
}
|
|
|
|
| 247 |
.addCase(fetchConversations.fulfilled, (state, action) => {
|
| 248 |
state.loading = false;
|
| 249 |
state.conversations = action.payload;
|
| 250 |
+
state.conversationsFetched = true;
|
| 251 |
})
|
| 252 |
.addCase(fetchConversations.rejected, (state, action) => {
|
| 253 |
state.loading = false;
|
src/styles/theme.css
CHANGED
|
@@ -15,19 +15,19 @@
|
|
| 15 |
/* ===================== DARK MODE ===================== */
|
| 16 |
.dark {
|
| 17 |
/* === SURFACE COLORS === */
|
| 18 |
-
--bg-surface: #
|
| 19 |
-
--bg-surface-secondary: #
|
| 20 |
-
--bg-surface-tertiary: #
|
| 21 |
|
| 22 |
/* === BORDER COLORS === */
|
| 23 |
-
--border-primary: #
|
| 24 |
-
--border-secondary: #
|
| 25 |
|
| 26 |
/* === TEXT COLORS === */
|
| 27 |
-
--text-primary: #
|
| 28 |
-
--text-secondary: #
|
| 29 |
-
--text-tertiary: #
|
| 30 |
-
--text-muted: #
|
| 31 |
|
| 32 |
/* === BRAND COLORS === */
|
| 33 |
--primary: #a3a6ff;
|
|
@@ -43,28 +43,28 @@
|
|
| 43 |
--tertiary-active: #ac8aff26;
|
| 44 |
|
| 45 |
/* === INPUT COLORS === */
|
| 46 |
-
--input-bg: #
|
| 47 |
-
--input-border: #
|
| 48 |
-
--input-text: #
|
| 49 |
-
--input-placeholder: #
|
| 50 |
|
| 51 |
/* === HOVER COLORS === */
|
| 52 |
-
--hover-primary: #
|
| 53 |
-
--hover-secondary: #
|
| 54 |
|
| 55 |
/* === CARD/BG COLORS === */
|
| 56 |
-
--card-bg: #
|
| 57 |
-
--card-bg-secondary: #
|
| 58 |
|
| 59 |
/* === STATUS COLORS === */
|
| 60 |
--online: #69f6b8;
|
| 61 |
-
--offline: #
|
| 62 |
--notification: #a3a6ff;
|
| 63 |
--danger: #ff6b6b;
|
| 64 |
|
| 65 |
/* === SCROLLBAR === */
|
| 66 |
-
--scrollbar-thumb: #
|
| 67 |
-
--scrollbar-thumb-hover: #
|
| 68 |
}
|
| 69 |
|
| 70 |
/* ===================== LIGHT MODE ===================== */
|
|
|
|
| 15 |
/* ===================== DARK MODE ===================== */
|
| 16 |
.dark {
|
| 17 |
/* === SURFACE COLORS === */
|
| 18 |
+
--bg-surface: #1e2238;
|
| 19 |
+
--bg-surface-secondary: #252b4a;
|
| 20 |
+
--bg-surface-tertiary: #2e3558;
|
| 21 |
|
| 22 |
/* === BORDER COLORS === */
|
| 23 |
+
--border-primary: #3d4568;
|
| 24 |
+
--border-secondary: #4a5478;
|
| 25 |
|
| 26 |
/* === TEXT COLORS === */
|
| 27 |
+
--text-primary: #f0f3f8;
|
| 28 |
+
--text-secondary: #c4d0e4;
|
| 29 |
+
--text-tertiary: #9aadcc;
|
| 30 |
+
--text-muted: #7a8db0;
|
| 31 |
|
| 32 |
/* === BRAND COLORS === */
|
| 33 |
--primary: #a3a6ff;
|
|
|
|
| 43 |
--tertiary-active: #ac8aff26;
|
| 44 |
|
| 45 |
/* === INPUT COLORS === */
|
| 46 |
+
--input-bg: #252b4a;
|
| 47 |
+
--input-border: #3d4568;
|
| 48 |
+
--input-text: #f0f3f8;
|
| 49 |
+
--input-placeholder: #9aadcc;
|
| 50 |
|
| 51 |
/* === HOVER COLORS === */
|
| 52 |
+
--hover-primary: #2e3558;
|
| 53 |
+
--hover-secondary: #3d4568;
|
| 54 |
|
| 55 |
/* === CARD/BG COLORS === */
|
| 56 |
+
--card-bg: #252b4a;
|
| 57 |
+
--card-bg-secondary: #2e3558;
|
| 58 |
|
| 59 |
/* === STATUS COLORS === */
|
| 60 |
--online: #69f6b8;
|
| 61 |
+
--offline: #9aadcc;
|
| 62 |
--notification: #a3a6ff;
|
| 63 |
--danger: #ff6b6b;
|
| 64 |
|
| 65 |
/* === SCROLLBAR === */
|
| 66 |
+
--scrollbar-thumb: #4a5478;
|
| 67 |
+
--scrollbar-thumb-hover: #5a6488;
|
| 68 |
}
|
| 69 |
|
| 70 |
/* ===================== LIGHT MODE ===================== */
|