AW-496-connect-chats
edits
This commit is contained in:
parent
e424cc7f23
commit
ccffd32511
@ -1,7 +1,7 @@
|
|||||||
.layout {
|
.layout {
|
||||||
position: relative;
|
position: relative;
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
padding-bottom: 120px;
|
padding-bottom: 220px;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
height: fit-content;
|
height: fit-content;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
.container {
|
.container {
|
||||||
padding: 38px 16px 120px;
|
padding: 38px 16px 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.categories {
|
.categories {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.main {
|
.main {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-bottom: 120px;
|
padding-bottom: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navBar {
|
.navBar {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
.main {
|
.main {
|
||||||
padding: 16px;
|
padding: 16px;
|
||||||
padding-bottom: 120px;
|
padding-bottom: 220px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.navBar {
|
.navBar {
|
||||||
|
|||||||
@ -2,4 +2,5 @@
|
|||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
flex: 1 1 0%;
|
flex: 1 1 0%;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
|
transition: padding-bottom 0.3s ease-in-out;
|
||||||
}
|
}
|
||||||
|
|
||||||
.loaderTop {
|
.loaderTop {
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import { useCallback, useEffect, useMemo, useRef } from "react";
|
|||||||
|
|
||||||
import { Spinner } from "@/components/ui";
|
import { Spinner } from "@/components/ui";
|
||||||
import { useChat } from "@/providers/chat-provider";
|
import { useChat } from "@/providers/chat-provider";
|
||||||
|
import { useChatStore } from "@/providers/chat-store-provider";
|
||||||
import { formatTime } from "@/shared/utils/date";
|
import { formatTime } from "@/shared/utils/date";
|
||||||
|
|
||||||
import { ChatMessages } from "..";
|
import { ChatMessages } from "..";
|
||||||
@ -21,6 +22,8 @@ export default function ChatMessagesWrapper() {
|
|||||||
scrollToBottom,
|
scrollToBottom,
|
||||||
} = useChat();
|
} = useChat();
|
||||||
|
|
||||||
|
const { suggestionsHeight, _hasHydrated } = useChatStore(state => state);
|
||||||
|
|
||||||
const isInitialScrollDone = useRef(false);
|
const isInitialScrollDone = useRef(false);
|
||||||
|
|
||||||
const handleScroll = useCallback(() => {
|
const handleScroll = useCallback(() => {
|
||||||
@ -45,20 +48,47 @@ export default function ChatMessagesWrapper() {
|
|||||||
}, [socketMessages]);
|
}, [socketMessages]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (socketMessages.length > 0 && !isInitialScrollDone.current) {
|
if (
|
||||||
|
socketMessages.length > 0 &&
|
||||||
|
!isInitialScrollDone.current &&
|
||||||
|
_hasHydrated
|
||||||
|
) {
|
||||||
scrollToBottom();
|
scrollToBottom();
|
||||||
const timeout = setTimeout(() => {
|
const timeout = setTimeout(() => {
|
||||||
isInitialScrollDone.current = true;
|
isInitialScrollDone.current = true;
|
||||||
}, 1000);
|
}, 1000);
|
||||||
return () => clearTimeout(timeout);
|
return () => clearTimeout(timeout);
|
||||||
}
|
}
|
||||||
}, [socketMessages.length, scrollToBottom]);
|
}, [socketMessages.length, scrollToBottom, _hasHydrated]);
|
||||||
|
|
||||||
|
// useEffect(() => {
|
||||||
|
// if (suggestionsHeight) {
|
||||||
|
// const timeout = setTimeout(() => {
|
||||||
|
// scrollToBottom();
|
||||||
|
// console.log("scrollToBottom 2");
|
||||||
|
// }, 0);
|
||||||
|
// return () => clearTimeout(timeout);
|
||||||
|
// }
|
||||||
|
// }, [suggestionsHeight, scrollToBottom]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (messagesWrapperRef.current?.style.paddingBottom) {
|
||||||
|
scrollToBottom();
|
||||||
|
}
|
||||||
|
}, [
|
||||||
|
scrollToBottom,
|
||||||
|
messagesWrapperRef.current?.style.paddingBottom,
|
||||||
|
messagesWrapperRef,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={styles.messagesWrapper}
|
className={styles.messagesWrapper}
|
||||||
ref={messagesWrapperRef}
|
ref={messagesWrapperRef}
|
||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
|
style={{
|
||||||
|
paddingBottom: suggestionsHeight,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{isLoadingOlder && hasMoreOlderMessages && (
|
{isLoadingOlder && hasMoreOlderMessages && (
|
||||||
<div className={styles.loaderTop}>
|
<div className={styles.loaderTop}>
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
.inputWrapper {
|
.container {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import { useChat } from "@/providers/chat-provider";
|
import { useChat } from "@/providers/chat-provider";
|
||||||
|
|
||||||
import { MessageInput } from "..";
|
import { MessageInput, Suggestions } from "..";
|
||||||
|
|
||||||
import styles from "./MessageInputWrapper.module.scss";
|
import styles from "./MessageInputWrapper.module.scss";
|
||||||
|
|
||||||
@ -10,8 +10,15 @@ export default function MessageInputWrapper() {
|
|||||||
const { send } = useChat();
|
const { send } = useChat();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.inputWrapper}>
|
<div className={styles.container}>
|
||||||
<MessageInput onSend={send} />
|
<Suggestions
|
||||||
|
onSuggestionClick={suggestion => {
|
||||||
|
send(suggestion);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className={styles.inputWrapper}>
|
||||||
|
<MessageInput onSend={send} />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,22 @@
|
|||||||
|
.container {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 12px;
|
||||||
|
padding: 12px 16px;
|
||||||
|
|
||||||
|
& > .suggestion {
|
||||||
|
width: fit-content;
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
border: 2px solid rgba(229, 231, 235, 1);
|
||||||
|
background: var(--background);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
& > .suggestionText {
|
||||||
|
color: #1f2937;
|
||||||
|
font-size: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
47
src/components/domains/chat/Suggestions/Suggestions.tsx
Normal file
47
src/components/domains/chat/Suggestions/Suggestions.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
|
||||||
|
import { Typography } from "@/components/ui";
|
||||||
|
import { useChatStore } from "@/providers/chat-store-provider";
|
||||||
|
|
||||||
|
import styles from "./Suggestions.module.scss";
|
||||||
|
|
||||||
|
interface SuggestionsProps {
|
||||||
|
onSuggestionClick: (suggestion: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Suggestions({ onSuggestionClick }: SuggestionsProps) {
|
||||||
|
const { suggestions, setSuggestionsHeight } = useChatStore(state => state);
|
||||||
|
const suggestionsRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSuggestionsHeight(suggestionsRef.current?.clientHeight ?? 0);
|
||||||
|
}, [setSuggestionsHeight, suggestions]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{!!suggestions?.length && (
|
||||||
|
<div className={styles.container} ref={suggestionsRef}>
|
||||||
|
{suggestions?.map((suggestion, index) => (
|
||||||
|
<div
|
||||||
|
key={`suggestion-${index}`}
|
||||||
|
className={styles.suggestion}
|
||||||
|
onClick={() => {
|
||||||
|
onSuggestionClick(suggestion);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
as="p"
|
||||||
|
weight="medium"
|
||||||
|
className={styles.suggestionText}
|
||||||
|
>
|
||||||
|
{suggestion}
|
||||||
|
</Typography>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -35,4 +35,5 @@ export {
|
|||||||
default as NewMessagesWrapper,
|
default as NewMessagesWrapper,
|
||||||
NewMessagesWrapperSkeleton,
|
NewMessagesWrapperSkeleton,
|
||||||
} from "./NewMessagesWrapper/NewMessagesWrapper";
|
} from "./NewMessagesWrapper/NewMessagesWrapper";
|
||||||
|
export { default as Suggestions } from "./Suggestions/Suggestions";
|
||||||
export { default as ViewAll, type ViewAllProps } from "./ViewAll/ViewAll";
|
export { default as ViewAll, type ViewAllProps } from "./ViewAll/ViewAll";
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|||||||
import { fetchChatMessages } from "@/entities/chats/actions";
|
import { fetchChatMessages } from "@/entities/chats/actions";
|
||||||
import type { IChatMessage } from "@/entities/chats/types";
|
import type { IChatMessage } from "@/entities/chats/types";
|
||||||
import { useSocketEvent } from "@/hooks/socket/useSocketEvent";
|
import { useSocketEvent } from "@/hooks/socket/useSocketEvent";
|
||||||
|
import { useChatStore } from "@/providers/chat-store-provider";
|
||||||
import {
|
import {
|
||||||
ESocketStatus,
|
ESocketStatus,
|
||||||
useSocketEmit,
|
useSocketEmit,
|
||||||
@ -13,7 +14,6 @@ import {
|
|||||||
} from "@/services/socket";
|
} from "@/services/socket";
|
||||||
import type {
|
import type {
|
||||||
ICurrentBalance,
|
ICurrentBalance,
|
||||||
IMessage,
|
|
||||||
IRefillModals,
|
IRefillModals,
|
||||||
ISessionStarted,
|
ISessionStarted,
|
||||||
} from "@/services/socket/events";
|
} from "@/services/socket/events";
|
||||||
@ -21,8 +21,8 @@ import type {
|
|||||||
const PAGE_LIMIT = 50;
|
const PAGE_LIMIT = 50;
|
||||||
|
|
||||||
type UIMessage = Pick<
|
type UIMessage = Pick<
|
||||||
IMessage,
|
IChatMessage,
|
||||||
"id" | "role" | "text" | "createdDate" | "isRead"
|
"id" | "role" | "text" | "createdDate" | "isRead" | "suggestions"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
interface UseChatSocketOptions {
|
interface UseChatSocketOptions {
|
||||||
@ -45,6 +45,7 @@ export const useChatSocket = (
|
|||||||
text: m.text,
|
text: m.text,
|
||||||
createdDate: m.createdDate,
|
createdDate: m.createdDate,
|
||||||
isRead: m.isRead,
|
isRead: m.isRead,
|
||||||
|
suggestions: m.suggestions,
|
||||||
});
|
});
|
||||||
|
|
||||||
const [messages, setMessages] = useState<UIMessage[]>(() =>
|
const [messages, setMessages] = useState<UIMessage[]>(() =>
|
||||||
@ -61,6 +62,9 @@ export const useChatSocket = (
|
|||||||
// const [isLoadingAdvisorMessage, setIsLoadingAdvisorMessage] = useState(false);
|
// const [isLoadingAdvisorMessage, setIsLoadingAdvisorMessage] = useState(false);
|
||||||
const [isSessionExpired, setIsSessionExpired] = useState(false);
|
const [isSessionExpired, setIsSessionExpired] = useState(false);
|
||||||
const [refillModals, setRefillModals] = useState<IRefillModals | null>(null);
|
const [refillModals, setRefillModals] = useState<IRefillModals | null>(null);
|
||||||
|
const { suggestions, setSuggestions } = useChatStore(state => state);
|
||||||
|
const [isSuggestionsInitialized, setIsSuggestionsInitialized] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
const isLoadingAdvisorMessage = useMemo(() => {
|
const isLoadingAdvisorMessage = useMemo(() => {
|
||||||
return messages.length > 0 && messages[0].role !== "assistant";
|
return messages.length > 0 && messages[0].role !== "assistant";
|
||||||
@ -162,6 +166,8 @@ export const useChatSocket = (
|
|||||||
if (data[0].role === "user") setIsLoadingSelfMessage(false);
|
if (data[0].role === "user") setIsLoadingSelfMessage(false);
|
||||||
// if (data[0].role === "assistant") setIsLoadingAdvisorMessage(false);
|
// if (data[0].role === "assistant") setIsLoadingAdvisorMessage(false);
|
||||||
|
|
||||||
|
setSuggestions(data[0].suggestions);
|
||||||
|
|
||||||
setMessages(prev => {
|
setMessages(prev => {
|
||||||
const map = new Map<string, UIMessage>();
|
const map = new Map<string, UIMessage>();
|
||||||
|
|
||||||
@ -223,6 +229,16 @@ export const useChatSocket = (
|
|||||||
};
|
};
|
||||||
}, [socket, status, joinChat, leaveChat, fetchBalance]);
|
}, [socket, status, joinChat, leaveChat, fetchBalance]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!isSuggestionsInitialized) {
|
||||||
|
setSuggestions([]);
|
||||||
|
}
|
||||||
|
if (messages[0]?.suggestions && !isSuggestionsInitialized) {
|
||||||
|
setSuggestions(messages[0].suggestions);
|
||||||
|
setIsSuggestionsInitialized(true);
|
||||||
|
}
|
||||||
|
}, [messages, setSuggestions, isSuggestionsInitialized]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session && status === ESocketStatus.CONNECTED) {
|
if (session && status === ESocketStatus.CONNECTED) {
|
||||||
startBalancePolling();
|
startBalancePolling();
|
||||||
@ -261,6 +277,7 @@ export const useChatSocket = (
|
|||||||
balance,
|
balance,
|
||||||
session,
|
session,
|
||||||
refillModals,
|
refillModals,
|
||||||
|
suggestions,
|
||||||
|
|
||||||
send,
|
send,
|
||||||
read,
|
read,
|
||||||
@ -281,6 +298,7 @@ export const useChatSocket = (
|
|||||||
balance,
|
balance,
|
||||||
session,
|
session,
|
||||||
refillModals,
|
refillModals,
|
||||||
|
suggestions,
|
||||||
isLoadingSelfMessage,
|
isLoadingSelfMessage,
|
||||||
isLoadingAdvisorMessage,
|
isLoadingAdvisorMessage,
|
||||||
isAvailableChatting,
|
isAvailableChatting,
|
||||||
|
|||||||
@ -1,18 +1,6 @@
|
|||||||
import { IGetChatsListResponse } from "@/entities/chats/types";
|
import { IChatMessage, IGetChatsListResponse } from "@/entities/chats/types";
|
||||||
import { Currency } from "@/types";
|
import { Currency } from "@/types";
|
||||||
|
|
||||||
export interface IMessage {
|
|
||||||
id: string;
|
|
||||||
role: string;
|
|
||||||
text?: string;
|
|
||||||
userId: string;
|
|
||||||
assistantId: string;
|
|
||||||
threadId: string;
|
|
||||||
chatId: string;
|
|
||||||
createdDate: string;
|
|
||||||
isRead: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ICurrentBalance {
|
export interface ICurrentBalance {
|
||||||
chatId: string;
|
chatId: string;
|
||||||
cost: number;
|
cost: number;
|
||||||
@ -79,7 +67,7 @@ export interface ServerToClientEventsBaseData<T> {
|
|||||||
export interface ServerToClientEvents {
|
export interface ServerToClientEvents {
|
||||||
chat_joined: (data: ServerToClientEventsBaseData<null>) => void;
|
chat_joined: (data: ServerToClientEventsBaseData<null>) => void;
|
||||||
chat_left: (data: ServerToClientEventsBaseData<boolean>) => void;
|
chat_left: (data: ServerToClientEventsBaseData<boolean>) => void;
|
||||||
receive_message: (data: IMessage[]) => void;
|
receive_message: (data: IChatMessage[]) => void;
|
||||||
current_balance: (
|
current_balance: (
|
||||||
data: ServerToClientEventsBaseData<ICurrentBalance>
|
data: ServerToClientEventsBaseData<ICurrentBalance>
|
||||||
) => void;
|
) => void;
|
||||||
|
|||||||
@ -95,7 +95,12 @@ export const useSocketStore = createStore<SocketStore>()(
|
|||||||
// Universal incoming event logger
|
// Universal incoming event logger
|
||||||
socket.onAny((event: string, ...args: unknown[]) => {
|
socket.onAny((event: string, ...args: unknown[]) => {
|
||||||
// Skip logging built-in socket.io events to avoid noise
|
// Skip logging built-in socket.io events to avoid noise
|
||||||
const systemEvents = ['connect', 'disconnect', 'connect_error', 'reconnect'];
|
const systemEvents = [
|
||||||
|
"connect",
|
||||||
|
"disconnect",
|
||||||
|
"connect_error",
|
||||||
|
"reconnect",
|
||||||
|
];
|
||||||
if (!systemEvents.includes(event)) {
|
if (!systemEvents.includes(event)) {
|
||||||
devLogger.socketIncoming(event, ...args);
|
devLogger.socketIncoming(event, ...args);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -61,19 +61,19 @@ class HttpClient {
|
|||||||
const startTime = Date.now();
|
const startTime = Date.now();
|
||||||
|
|
||||||
// Log API request (both client and server with ENV control)
|
// Log API request (both client and server with ENV control)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
// Client-side logging
|
// Client-side logging
|
||||||
devLogger.apiRequest(fullUrl, method, body);
|
devLogger.apiRequest(fullUrl, method, body);
|
||||||
} else {
|
} else {
|
||||||
// Server-side logging (requires ENV variable)
|
// Server-side logging (requires ENV variable)
|
||||||
if (typeof devLogger.serverApiRequest === 'function') {
|
if (typeof devLogger.serverApiRequest === "function") {
|
||||||
devLogger.serverApiRequest(fullUrl, method, body);
|
devLogger.serverApiRequest(fullUrl, method, body);
|
||||||
} else {
|
} else {
|
||||||
// Fallback server logging
|
// Fallback server logging
|
||||||
if (process.env.DEV_LOGGER_SERVER_ENABLED === 'true') {
|
if (process.env.DEV_LOGGER_SERVER_ENABLED === "true") {
|
||||||
console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${fullUrl}`);
|
console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${fullUrl}`);
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
console.log('📦 Request Body:', JSON.stringify(body, null, 2));
|
console.log("📦 Request Body:", JSON.stringify(body, null, 2));
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
@ -98,20 +98,28 @@ class HttpClient {
|
|||||||
|
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
// Log API error response (both client and server)
|
// Log API error response (both client and server)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
devLogger.apiResponse(fullUrl, method, res.status, payload, duration);
|
devLogger.apiResponse(fullUrl, method, res.status, payload, duration);
|
||||||
} else {
|
} else {
|
||||||
if (typeof devLogger.serverApiResponse === 'function') {
|
if (typeof devLogger.serverApiResponse === "function") {
|
||||||
devLogger.serverApiResponse(fullUrl, method, res.status, payload, duration);
|
devLogger.serverApiResponse(
|
||||||
|
fullUrl,
|
||||||
|
method,
|
||||||
|
res.status,
|
||||||
|
payload,
|
||||||
|
duration
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Fallback server logging
|
// Fallback server logging
|
||||||
if (process.env.DEV_LOGGER_SERVER_ENABLED === 'true') {
|
if (process.env.DEV_LOGGER_SERVER_ENABLED === "true") {
|
||||||
const emoji = res.status >= 200 && res.status < 300 ? '✅' : '❌';
|
const emoji = res.status >= 200 && res.status < 300 ? "✅" : "❌";
|
||||||
console.group(`\n${emoji} [SERVER] API ERROR: ${method} ${fullUrl}`);
|
console.group(
|
||||||
|
`\n${emoji} [SERVER] API ERROR: ${method} ${fullUrl}`
|
||||||
|
);
|
||||||
console.log(`📊 Status: ${res.status}`);
|
console.log(`📊 Status: ${res.status}`);
|
||||||
console.log(`⏱️ Duration: ${duration}ms`);
|
console.log(`⏱️ Duration: ${duration}ms`);
|
||||||
if (payload !== undefined) {
|
if (payload !== undefined) {
|
||||||
console.log('📦 Error Response:', payload);
|
console.log("📦 Error Response:", payload);
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
@ -128,24 +136,37 @@ class HttpClient {
|
|||||||
const validatedData = schema ? schema.parse(data) : data;
|
const validatedData = schema ? schema.parse(data) : data;
|
||||||
|
|
||||||
// Log successful API response (both client and server)
|
// Log successful API response (both client and server)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
devLogger.apiResponse(fullUrl, method, res.status, validatedData, duration);
|
devLogger.apiResponse(
|
||||||
|
fullUrl,
|
||||||
|
method,
|
||||||
|
res.status,
|
||||||
|
validatedData,
|
||||||
|
duration
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
if (typeof devLogger.serverApiResponse === 'function') {
|
if (typeof devLogger.serverApiResponse === "function") {
|
||||||
devLogger.serverApiResponse(fullUrl, method, res.status, validatedData, duration);
|
devLogger.serverApiResponse(
|
||||||
|
fullUrl,
|
||||||
|
method,
|
||||||
|
res.status,
|
||||||
|
validatedData,
|
||||||
|
duration
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// Fallback server logging
|
// Fallback server logging
|
||||||
if (process.env.DEV_LOGGER_SERVER_ENABLED === 'true') {
|
if (process.env.DEV_LOGGER_SERVER_ENABLED === "true") {
|
||||||
console.group(`\n✅ [SERVER] API SUCCESS: ${method} ${fullUrl}`);
|
console.group(`\n✅ [SERVER] API SUCCESS: ${method} ${fullUrl}`);
|
||||||
console.log(`📊 Status: ${res.status}`);
|
console.log(`📊 Status: ${res.status}`);
|
||||||
console.log(`⏱️ Duration: ${duration}ms`);
|
console.log(`⏱️ Duration: ${duration}ms`);
|
||||||
if (validatedData !== undefined) {
|
if (validatedData !== undefined) {
|
||||||
const responsePreview = typeof validatedData === 'object' && validatedData !== null
|
const responsePreview =
|
||||||
? Array.isArray(validatedData)
|
typeof validatedData === "object" && validatedData !== null
|
||||||
? `Array[${validatedData.length}]`
|
? Array.isArray(validatedData)
|
||||||
: `Object{${Object.keys(validatedData).slice(0, 5).join(', ')}${Object.keys(validatedData).length > 5 ? '...' : ''}}`
|
? `Array[${validatedData.length}]`
|
||||||
: validatedData;
|
: `Object{${Object.keys(validatedData).slice(0, 5).join(", ")}${Object.keys(validatedData).length > 5 ? "..." : ""}}`
|
||||||
console.log('📦 Response Preview:', responsePreview);
|
: validatedData;
|
||||||
|
console.log("📦 Response Preview:", responsePreview);
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,17 @@
|
|||||||
/* eslint-disable no-console */
|
/* eslint-disable no-console */
|
||||||
|
|
||||||
export enum LogType {
|
export enum LogType {
|
||||||
API = 'API',
|
API = "API",
|
||||||
SOCKET = 'SOCKET',
|
SOCKET = "SOCKET",
|
||||||
ERROR = 'ERROR',
|
ERROR = "ERROR",
|
||||||
INFO = 'INFO'
|
INFO = "INFO",
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum LogDirection {
|
export enum LogDirection {
|
||||||
REQUEST = 'REQUEST',
|
REQUEST = "REQUEST",
|
||||||
RESPONSE = 'RESPONSE',
|
RESPONSE = "RESPONSE",
|
||||||
INCOMING = 'INCOMING',
|
INCOMING = "INCOMING",
|
||||||
OUTGOING = 'OUTGOING'
|
OUTGOING = "OUTGOING",
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LogEntry {
|
interface LogEntry {
|
||||||
@ -36,20 +36,21 @@ class DevLogger {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Check ENV variables first
|
// Check ENV variables first
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
this.envEnabled = process.env.NEXT_PUBLIC_DEV_LOGGER_ENABLED !== 'false';
|
this.envEnabled = process.env.NEXT_PUBLIC_DEV_LOGGER_ENABLED !== "false";
|
||||||
} else {
|
} else {
|
||||||
// Server side - check server env
|
// Server side - check server env
|
||||||
this.serverLoggingEnabled = process.env.DEV_LOGGER_SERVER_ENABLED === 'true';
|
this.serverLoggingEnabled =
|
||||||
this.envEnabled = process.env.DEV_LOGGER_ENABLED !== 'false';
|
process.env.DEV_LOGGER_SERVER_ENABLED === "true";
|
||||||
|
this.envEnabled = process.env.DEV_LOGGER_ENABLED !== "false";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check localStorage for logging preferences (client-side only)
|
// Check localStorage for logging preferences (client-side only)
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
const stored = localStorage.getItem('dev-logger-enabled');
|
const stored = localStorage.getItem("dev-logger-enabled");
|
||||||
this.enabled = stored ? JSON.parse(stored) : this.envEnabled;
|
this.enabled = stored ? JSON.parse(stored) : this.envEnabled;
|
||||||
|
|
||||||
const storedTypes = localStorage.getItem('dev-logger-types');
|
const storedTypes = localStorage.getItem("dev-logger-types");
|
||||||
if (storedTypes) {
|
if (storedTypes) {
|
||||||
this.enabledTypes = new Set(JSON.parse(storedTypes));
|
this.enabledTypes = new Set(JSON.parse(storedTypes));
|
||||||
}
|
}
|
||||||
@ -65,81 +66,114 @@ class DevLogger {
|
|||||||
|
|
||||||
private shouldLogServer(type: LogType): boolean {
|
private shouldLogServer(type: LogType): boolean {
|
||||||
// Server logging requires explicit ENV enable
|
// Server logging requires explicit ENV enable
|
||||||
return this.serverLoggingEnabled && this.envEnabled && this.enabled && this.enabledTypes.has(type);
|
return (
|
||||||
|
this.serverLoggingEnabled &&
|
||||||
|
this.envEnabled &&
|
||||||
|
this.enabled &&
|
||||||
|
this.enabledTypes.has(type)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private getLogStyle(type: LogType, direction?: LogDirection): { emoji: string; color: string; bgColor?: string } {
|
private getLogStyle(
|
||||||
|
type: LogType,
|
||||||
|
direction?: LogDirection
|
||||||
|
): { emoji: string; color: string; bgColor?: string } {
|
||||||
const styles: Record<LogType, any> = {
|
const styles: Record<LogType, any> = {
|
||||||
[LogType.API]: {
|
[LogType.API]: {
|
||||||
[LogDirection.REQUEST]: { emoji: '🚀', color: '#3b82f6', bgColor: '#eff6ff' },
|
[LogDirection.REQUEST]: {
|
||||||
[LogDirection.RESPONSE]: { emoji: '📨', color: '#10b981', bgColor: '#f0fdf4' },
|
emoji: "🚀",
|
||||||
|
color: "#3b82f6",
|
||||||
|
bgColor: "#eff6ff",
|
||||||
|
},
|
||||||
|
[LogDirection.RESPONSE]: {
|
||||||
|
emoji: "📨",
|
||||||
|
color: "#10b981",
|
||||||
|
bgColor: "#f0fdf4",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
[LogType.SOCKET]: {
|
[LogType.SOCKET]: {
|
||||||
[LogDirection.OUTGOING]: { emoji: '🟢', color: '#16a34a' },
|
[LogDirection.OUTGOING]: { emoji: "🟢", color: "#16a34a" },
|
||||||
[LogDirection.INCOMING]: { emoji: '🔵', color: '#2563eb' },
|
[LogDirection.INCOMING]: { emoji: "🔵", color: "#2563eb" },
|
||||||
},
|
},
|
||||||
[LogType.ERROR]: { emoji: '❌', color: '#ef4444' },
|
[LogType.ERROR]: { emoji: "❌", color: "#ef4444" },
|
||||||
[LogType.INFO]: { emoji: 'ℹ️', color: '#6366f1' }
|
[LogType.INFO]: { emoji: "ℹ️", color: "#6366f1" },
|
||||||
};
|
};
|
||||||
|
|
||||||
const typeStyles = styles[type];
|
const typeStyles = styles[type];
|
||||||
if (direction && typeof typeStyles === 'object' && direction in typeStyles) {
|
if (
|
||||||
|
direction &&
|
||||||
|
typeof typeStyles === "object" &&
|
||||||
|
direction in typeStyles
|
||||||
|
) {
|
||||||
return typeStyles[direction];
|
return typeStyles[direction];
|
||||||
}
|
}
|
||||||
return typeof typeStyles === 'object' ? { emoji: '📝', color: '#6b7280' } : typeStyles;
|
return typeof typeStyles === "object"
|
||||||
|
? { emoji: "📝", color: "#6b7280" }
|
||||||
|
: typeStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
private formatTime(date: Date): string {
|
private formatTime(date: Date): string {
|
||||||
return date.toLocaleTimeString('en-US', {
|
return date.toLocaleTimeString("en-US", {
|
||||||
hour12: false,
|
hour12: false,
|
||||||
hour: '2-digit',
|
hour: "2-digit",
|
||||||
minute: '2-digit',
|
minute: "2-digit",
|
||||||
second: '2-digit',
|
second: "2-digit",
|
||||||
fractionalSecondDigits: 3
|
fractionalSecondDigits: 3,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
log(entry: Omit<LogEntry, 'timestamp'>) {
|
log(entry: Omit<LogEntry, "timestamp">) {
|
||||||
if (!this.shouldLog(entry.type)) return;
|
if (!this.shouldLog(entry.type)) return;
|
||||||
|
|
||||||
const timestamp = new Date();
|
const timestamp = new Date();
|
||||||
const { emoji, color, bgColor } = this.getLogStyle(entry.type, entry.direction);
|
const { emoji, color, bgColor } = this.getLogStyle(
|
||||||
|
entry.type,
|
||||||
|
entry.direction
|
||||||
|
);
|
||||||
const timeStr = this.formatTime(timestamp);
|
const timeStr = this.formatTime(timestamp);
|
||||||
const baseStyle = `color: ${color}; font-weight: bold;`;
|
const baseStyle = `color: ${color}; font-weight: bold;`;
|
||||||
const groupStyle = bgColor ? `${baseStyle} background: ${bgColor}; padding: 2px 6px; border-radius: 3px;` : baseStyle;
|
const groupStyle = bgColor
|
||||||
|
? `${baseStyle} background: ${bgColor}; padding: 2px 6px; border-radius: 3px;`
|
||||||
|
: baseStyle;
|
||||||
|
|
||||||
// Create compact collapsible group
|
// Create compact collapsible group
|
||||||
const groupTitle = `${emoji} ${entry.type}${entry.direction ? ` ${entry.direction}` : ''}: ${entry.event}`;
|
const groupTitle = `${emoji} ${entry.type}${entry.direction ? ` ${entry.direction}` : ""}: ${entry.event}`;
|
||||||
|
|
||||||
// Always use groupCollapsed for cleaner output
|
// Always use groupCollapsed for cleaner output
|
||||||
console.groupCollapsed(
|
console.groupCollapsed(`%c${groupTitle} [${timeStr}]`, groupStyle);
|
||||||
`%c${groupTitle} [${timeStr}]`,
|
|
||||||
groupStyle
|
|
||||||
);
|
|
||||||
|
|
||||||
// Compact one-line summary with key info
|
// Compact one-line summary with key info
|
||||||
const summaryParts = [];
|
const summaryParts = [];
|
||||||
if (entry.method) summaryParts.push(`${entry.method}`);
|
if (entry.method) summaryParts.push(`${entry.method}`);
|
||||||
if (entry.status) {
|
if (entry.status) {
|
||||||
const statusColor = entry.status >= 200 && entry.status < 300 ? '✅' : '❌';
|
const statusColor =
|
||||||
|
entry.status >= 200 && entry.status < 300 ? "✅" : "❌";
|
||||||
summaryParts.push(`${statusColor} ${entry.status}`);
|
summaryParts.push(`${statusColor} ${entry.status}`);
|
||||||
}
|
}
|
||||||
if (entry.duration !== undefined) summaryParts.push(`⏱️ ${entry.duration}ms`);
|
if (entry.duration !== undefined)
|
||||||
|
summaryParts.push(`⏱️ ${entry.duration}ms`);
|
||||||
|
|
||||||
if (summaryParts.length > 0) {
|
if (summaryParts.length > 0) {
|
||||||
console.log(`%c${summaryParts.join(' • ')}`, 'color: #6b7280; font-size: 11px;');
|
console.log(
|
||||||
|
`%c${summaryParts.join(" • ")}`,
|
||||||
|
"color: #6b7280; font-size: 11px;"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (entry.data !== undefined) {
|
if (entry.data !== undefined) {
|
||||||
// Show preview for objects/arrays, full value for primitives
|
// Show preview for objects/arrays, full value for primitives
|
||||||
if (typeof entry.data === 'object' && entry.data !== null) {
|
if (typeof entry.data === "object" && entry.data !== null) {
|
||||||
const preview = Array.isArray(entry.data)
|
const preview = Array.isArray(entry.data)
|
||||||
? `Array[${entry.data.length}]`
|
? `Array[${entry.data.length}]`
|
||||||
: `Object{${Object.keys(entry.data).slice(0, 3).join(', ')}${Object.keys(entry.data).length > 3 ? '...' : ''}}`;
|
: `Object{${Object.keys(entry.data).slice(0, 3).join(", ")}${Object.keys(entry.data).length > 3 ? "..." : ""}}`;
|
||||||
console.log(`%c📦 Data:`, 'color: #6b7280; font-size: 11px;', preview);
|
console.log(`%c📦 Data:`, "color: #6b7280; font-size: 11px;", preview);
|
||||||
console.log(entry.data);
|
console.log(entry.data);
|
||||||
} else {
|
} else {
|
||||||
console.log(`%c📦 Data:`, 'color: #6b7280; font-size: 11px;', entry.data);
|
console.log(
|
||||||
|
`%c📦 Data:`,
|
||||||
|
"color: #6b7280; font-size: 11px;",
|
||||||
|
entry.data
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,23 +185,29 @@ class DevLogger {
|
|||||||
this.log({
|
this.log({
|
||||||
type: LogType.API,
|
type: LogType.API,
|
||||||
direction: LogDirection.REQUEST,
|
direction: LogDirection.REQUEST,
|
||||||
event: `${method.toUpperCase()} ${url.split('?')[0]}`,
|
event: `${method.toUpperCase()} ${url.split("?")[0]}`,
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
apiResponse(url: string, method: string, status: number, data?: unknown, duration?: number) {
|
apiResponse(
|
||||||
|
url: string,
|
||||||
|
method: string,
|
||||||
|
status: number,
|
||||||
|
data?: unknown,
|
||||||
|
duration?: number
|
||||||
|
) {
|
||||||
this.log({
|
this.log({
|
||||||
type: LogType.API,
|
type: LogType.API,
|
||||||
direction: LogDirection.RESPONSE,
|
direction: LogDirection.RESPONSE,
|
||||||
event: `${method.toUpperCase()} ${url.split('?')[0]}`,
|
event: `${method.toUpperCase()} ${url.split("?")[0]}`,
|
||||||
url,
|
url,
|
||||||
method,
|
method,
|
||||||
status,
|
status,
|
||||||
data,
|
data,
|
||||||
duration
|
duration,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,7 +217,7 @@ class DevLogger {
|
|||||||
type: LogType.SOCKET,
|
type: LogType.SOCKET,
|
||||||
direction: LogDirection.OUTGOING,
|
direction: LogDirection.OUTGOING,
|
||||||
event,
|
event,
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,7 +226,7 @@ class DevLogger {
|
|||||||
type: LogType.SOCKET,
|
type: LogType.SOCKET,
|
||||||
direction: LogDirection.INCOMING,
|
direction: LogDirection.INCOMING,
|
||||||
event,
|
event,
|
||||||
data
|
data,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,23 +234,23 @@ class DevLogger {
|
|||||||
socketConnected() {
|
socketConnected() {
|
||||||
console.log(
|
console.log(
|
||||||
`%c✅ SOCKET CONNECTED`,
|
`%c✅ SOCKET CONNECTED`,
|
||||||
'color: #10b981; font-weight: bold; background: #f0fdf4; padding: 2px 6px; border-radius: 3px;'
|
"color: #10b981; font-weight: bold; background: #f0fdf4; padding: 2px 6px; border-radius: 3px;"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
socketDisconnected(reason?: string) {
|
socketDisconnected(reason?: string) {
|
||||||
console.log(
|
console.log(
|
||||||
`%c❌ SOCKET DISCONNECTED`,
|
`%c❌ SOCKET DISCONNECTED`,
|
||||||
'color: #ef4444; font-weight: bold; background: #fef2f2; padding: 2px 6px; border-radius: 3px;',
|
"color: #ef4444; font-weight: bold; background: #fef2f2; padding: 2px 6px; border-radius: 3px;",
|
||||||
reason || ''
|
reason || ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
socketError(error?: unknown) {
|
socketError(error?: unknown) {
|
||||||
console.log(
|
console.log(
|
||||||
`%c⚠️ SOCKET ERROR`,
|
`%c⚠️ SOCKET ERROR`,
|
||||||
'color: #f59e0b; font-weight: bold; background: #fffbeb; padding: 2px 6px; border-radius: 3px;',
|
"color: #f59e0b; font-weight: bold; background: #fffbeb; padding: 2px 6px; border-radius: 3px;",
|
||||||
error || ''
|
error || ""
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,30 +260,39 @@ class DevLogger {
|
|||||||
|
|
||||||
console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${url}`);
|
console.group(`\n🚀 [SERVER] API REQUEST: ${method} ${url}`);
|
||||||
if (body !== undefined) {
|
if (body !== undefined) {
|
||||||
console.log('📦 Request Body:', JSON.stringify(body, null, 2));
|
console.log("📦 Request Body:", JSON.stringify(body, null, 2));
|
||||||
}
|
}
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
|
|
||||||
serverApiResponse(url: string, method: string, status: number, data?: unknown, duration?: number) {
|
serverApiResponse(
|
||||||
|
url: string,
|
||||||
|
method: string,
|
||||||
|
status: number,
|
||||||
|
data?: unknown,
|
||||||
|
duration?: number
|
||||||
|
) {
|
||||||
if (!this.shouldLogServer(LogType.API)) return;
|
if (!this.shouldLogServer(LogType.API)) return;
|
||||||
|
|
||||||
const emoji = status >= 200 && status < 300 ? '✅' : '❌';
|
const emoji = status >= 200 && status < 300 ? "✅" : "❌";
|
||||||
console.group(`\n${emoji} [SERVER] API ${status >= 200 && status < 300 ? 'SUCCESS' : 'ERROR'}: ${method} ${url}`);
|
console.group(
|
||||||
|
`\n${emoji} [SERVER] API ${status >= 200 && status < 300 ? "SUCCESS" : "ERROR"}: ${method} ${url}`
|
||||||
|
);
|
||||||
console.log(`📊 Status: ${status}`);
|
console.log(`📊 Status: ${status}`);
|
||||||
if (duration !== undefined) {
|
if (duration !== undefined) {
|
||||||
console.log(`⏱️ Duration: ${duration}ms`);
|
console.log(`⏱️ Duration: ${duration}ms`);
|
||||||
}
|
}
|
||||||
if (data !== undefined) {
|
if (data !== undefined) {
|
||||||
// Limit response data display to avoid overwhelming logs
|
// Limit response data display to avoid overwhelming logs
|
||||||
const responsePreview = typeof data === 'object' && data !== null
|
const responsePreview =
|
||||||
? Array.isArray(data)
|
typeof data === "object" && data !== null
|
||||||
? `Array[${data.length}]`
|
? Array.isArray(data)
|
||||||
: `Object{${Object.keys(data).slice(0, 5).join(', ')}${Object.keys(data).length > 5 ? '...' : ''}}`
|
? `Array[${data.length}]`
|
||||||
: data;
|
: `Object{${Object.keys(data).slice(0, 5).join(", ")}${Object.keys(data).length > 5 ? "..." : ""}}`
|
||||||
console.log('📦 Response Preview:', responsePreview);
|
: data;
|
||||||
|
console.log("📦 Response Preview:", responsePreview);
|
||||||
// Full response data (collapsed)
|
// Full response data (collapsed)
|
||||||
console.groupCollapsed('📄 Full Response Data:');
|
console.groupCollapsed("📄 Full Response Data:");
|
||||||
console.log(data);
|
console.log(data);
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
@ -253,43 +302,61 @@ class DevLogger {
|
|||||||
// Control methods
|
// Control methods
|
||||||
enable() {
|
enable() {
|
||||||
this.enabled = true;
|
this.enabled = true;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem('dev-logger-enabled', 'true');
|
localStorage.setItem("dev-logger-enabled", "true");
|
||||||
}
|
}
|
||||||
console.log('%c📝 Dev Logger ENABLED', 'color: #10b981; font-weight: bold;');
|
console.log(
|
||||||
|
"%c📝 Dev Logger ENABLED",
|
||||||
|
"color: #10b981; font-weight: bold;"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disable() {
|
disable() {
|
||||||
this.enabled = false;
|
this.enabled = false;
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem('dev-logger-enabled', 'false');
|
localStorage.setItem("dev-logger-enabled", "false");
|
||||||
}
|
}
|
||||||
console.log('%c📝 Dev Logger DISABLED', 'color: #ef4444; font-weight: bold;');
|
console.log(
|
||||||
|
"%c📝 Dev Logger DISABLED",
|
||||||
|
"color: #ef4444; font-weight: bold;"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
enableType(type: LogType) {
|
enableType(type: LogType) {
|
||||||
this.enabledTypes.add(type);
|
this.enabledTypes.add(type);
|
||||||
this.saveEnabledTypes();
|
this.saveEnabledTypes();
|
||||||
console.log(`%c📝 ${type} logging ENABLED`, 'color: #10b981; font-weight: bold;');
|
console.log(
|
||||||
|
`%c📝 ${type} logging ENABLED`,
|
||||||
|
"color: #10b981; font-weight: bold;"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
disableType(type: LogType) {
|
disableType(type: LogType) {
|
||||||
this.enabledTypes.delete(type);
|
this.enabledTypes.delete(type);
|
||||||
this.saveEnabledTypes();
|
this.saveEnabledTypes();
|
||||||
console.log(`%c📝 ${type} logging DISABLED`, 'color: #ef4444; font-weight: bold;');
|
console.log(
|
||||||
|
`%c📝 ${type} logging DISABLED`,
|
||||||
|
"color: #ef4444; font-weight: bold;"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private saveEnabledTypes() {
|
private saveEnabledTypes() {
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
localStorage.setItem('dev-logger-types', JSON.stringify(Array.from(this.enabledTypes)));
|
localStorage.setItem(
|
||||||
|
"dev-logger-types",
|
||||||
|
JSON.stringify(Array.from(this.enabledTypes))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to show current settings
|
// Helper method to show current settings
|
||||||
status() {
|
status() {
|
||||||
console.group('%c🔧 Dev Logger Status', 'color: #6366f1; font-weight: bold;');
|
console.group(
|
||||||
console.log('Enabled:', this.enabled);
|
"%c🔧 Dev Logger Status",
|
||||||
console.log('Active Types:', Array.from(this.enabledTypes));
|
"color: #6366f1; font-weight: bold;"
|
||||||
|
);
|
||||||
|
console.log("Enabled:", this.enabled);
|
||||||
|
console.log("Active Types:", Array.from(this.enabledTypes));
|
||||||
console.groupEnd();
|
console.groupEnd();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -298,7 +365,7 @@ class DevLogger {
|
|||||||
export const devLogger = new DevLogger();
|
export const devLogger = new DevLogger();
|
||||||
|
|
||||||
// Make it available globally for easy console access
|
// Make it available globally for easy console access
|
||||||
if (typeof window !== 'undefined') {
|
if (typeof window !== "undefined") {
|
||||||
(window as any).devLogger = devLogger;
|
(window as any).devLogger = devLogger;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -91,7 +91,6 @@ export const createAppUiStore = (initState: AppUiState = initialState) => {
|
|||||||
{
|
{
|
||||||
name: "app-ui-storage",
|
name: "app-ui-storage",
|
||||||
onRehydrateStorage: () => state => {
|
onRehydrateStorage: () => state => {
|
||||||
// Вызывается после загрузки данных из localStorage
|
|
||||||
if (state) {
|
if (state) {
|
||||||
state.setHasHydrated(true);
|
state.setHasHydrated(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,17 +3,23 @@
|
|||||||
import { createStore } from "zustand";
|
import { createStore } from "zustand";
|
||||||
import { persist } from "zustand/middleware";
|
import { persist } from "zustand/middleware";
|
||||||
|
|
||||||
import { IChat } from "@/entities/chats/types";
|
import { IChat, IChatMessage } from "@/entities/chats/types";
|
||||||
|
|
||||||
interface ChatState {
|
interface ChatState {
|
||||||
currentChat: IChat | null;
|
currentChat: IChat | null;
|
||||||
isAutoTopUp: boolean;
|
isAutoTopUp: boolean;
|
||||||
|
suggestions: IChatMessage["suggestions"];
|
||||||
|
suggestionsHeight: number;
|
||||||
|
_hasHydrated: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ChatActions = {
|
export type ChatActions = {
|
||||||
setCurrentChat: (chat: IChat) => void;
|
setCurrentChat: (chat: IChat) => void;
|
||||||
setIsAutoTopUp: (isAutoTopUp: boolean) => void;
|
setIsAutoTopUp: (isAutoTopUp: boolean) => void;
|
||||||
|
setSuggestions: (suggestions: IChatMessage["suggestions"]) => void;
|
||||||
|
setSuggestionsHeight: (height: number) => void;
|
||||||
clearChatData: () => void;
|
clearChatData: () => void;
|
||||||
|
setHasHydrated: (hasHydrated: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ChatStore = ChatState & ChatActions;
|
export type ChatStore = ChatState & ChatActions;
|
||||||
@ -21,6 +27,9 @@ export type ChatStore = ChatState & ChatActions;
|
|||||||
const initialState: ChatState = {
|
const initialState: ChatState = {
|
||||||
currentChat: null,
|
currentChat: null,
|
||||||
isAutoTopUp: false,
|
isAutoTopUp: false,
|
||||||
|
suggestions: [],
|
||||||
|
suggestionsHeight: 0,
|
||||||
|
_hasHydrated: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createChatStore = (initState: ChatState = initialState) => {
|
export const createChatStore = (initState: ChatState = initialState) => {
|
||||||
@ -30,9 +39,23 @@ export const createChatStore = (initState: ChatState = initialState) => {
|
|||||||
...initState,
|
...initState,
|
||||||
setCurrentChat: (chat: IChat) => set({ currentChat: chat }),
|
setCurrentChat: (chat: IChat) => set({ currentChat: chat }),
|
||||||
setIsAutoTopUp: (isAutoTopUp: boolean) => set({ isAutoTopUp }),
|
setIsAutoTopUp: (isAutoTopUp: boolean) => set({ isAutoTopUp }),
|
||||||
|
setSuggestions: (suggestions: IChatMessage["suggestions"]) =>
|
||||||
|
set({ suggestions }),
|
||||||
|
setSuggestionsHeight: (height: number) =>
|
||||||
|
set({ suggestionsHeight: height }),
|
||||||
clearChatData: () => set(initialState),
|
clearChatData: () => set(initialState),
|
||||||
|
|
||||||
|
setHasHydrated: (hasHydrated: boolean) =>
|
||||||
|
set({ _hasHydrated: hasHydrated }),
|
||||||
}),
|
}),
|
||||||
{ name: "chat-storage" }
|
{
|
||||||
|
name: "chat-storage",
|
||||||
|
onRehydrateStorage: () => state => {
|
||||||
|
if (state) {
|
||||||
|
state.setHasHydrated(true);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user