diff --git a/apps/chat/src/app/pages/channel/ChannelVoice.tsx b/apps/chat/src/app/pages/channel/ChannelVoice.tsx index 020e82d64d..e6a5c450fe 100644 --- a/apps/chat/src/app/pages/channel/ChannelVoice.tsx +++ b/apps/chat/src/app/pages/channel/ChannelVoice.tsx @@ -9,6 +9,7 @@ import { handleParticipantMeetState, selectCurrentChannel, selectCurrentClan, + selectIsShowSettingFooter, selectShowCamera, selectShowMicrophone, selectVoiceFullScreen, @@ -128,9 +129,11 @@ const ChannelVoice = memo( const isShow = isJoined && voiceInfo?.clanId === currentChannel?.clan_id && voiceInfo?.channelId === currentChannel?.channel_id; + const isShowSettingFooter = useSelector(selectIsShowSettingFooter); + return (
{tokenRef.current === '' || !serverUrl ? ( diff --git a/apps/chat/src/app/pages/main/index.tsx b/apps/chat/src/app/pages/main/index.tsx index 275f9e2ed2..f922b939ad 100644 --- a/apps/chat/src/app/pages/main/index.tsx +++ b/apps/chat/src/app/pages/main/index.tsx @@ -52,7 +52,7 @@ import { selectSignalingDataByUserId, selectStatusMenu, selectTheme, - selectToastErrorStatus, + selectToastErrors, useAppDispatch, useAppSelector } from '@mezon/store'; @@ -276,56 +276,52 @@ function MyApp() { const handleClose = () => { dispatch(e2eeActions.setOpenModalE2ee(false)); }; - // show toast error - const toastErrorStatus = useSelector(selectToastErrorStatus); - const [openUnknown, closeUnknown] = useModal(() => { - return ; - }, []); - useEffect(() => { - if (toastErrorStatus) { - openUnknown(); - } else { - closeUnknown(); - } - }, [toastErrorStatus]); + + const toastError = useSelector(selectToastErrors); + return ( -
- {previewMode && } - {openPopupForward && } - - + <> + {toastError.map((error) => ( + + ))}
- -
+ {previewMode && } + {openPopupForward && } + + +
+ +
- {isPlayRingTone && - !!dataCall && - !isInCall && - directId !== dataCall?.channel_id && - dataCall?.data_type === WebrtcSignalingType.WEBRTC_SDP_OFFER && ( - - )} - - - {openModalE2ee && !hasKeyE2ee && } - {openModalAttachment && } - {isShowFirstJoinPopup && setIsShowFirstJoinPopup(false)} />} - {isShowPopupQuickMess && } -
+ {isPlayRingTone && + !!dataCall && + !isInCall && + directId !== dataCall?.channel_id && + dataCall?.data_type === WebrtcSignalingType.WEBRTC_SDP_OFFER && ( + + )} + + + {openModalE2ee && !hasKeyE2ee && } + {openModalAttachment && } + {isShowFirstJoinPopup && setIsShowFirstJoinPopup(false)} />} + {isShowPopupQuickMess && } +
+ ); } diff --git a/libs/components/src/lib/components/FooterProfile/index.tsx b/libs/components/src/lib/components/FooterProfile/index.tsx index dca65a5988..2d2e14084d 100644 --- a/libs/components/src/lib/components/FooterProfile/index.tsx +++ b/libs/components/src/lib/components/FooterProfile/index.tsx @@ -58,7 +58,13 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp const [userSearchError, setUserSearchError] = useState(null); const [resetTimerStatus, setResetTimerStatus] = useState(0); const [noClearStatus, setNoClearStatus] = useState(false); - const [isInputDisabled, setIsInputDisabled] = useState(false); + const [sendTokenInputsState, setSendTokenInputsState] = useState<{ + isSendTokenInputDisabled: boolean; + isUserSelectionDisabled: boolean; + }>({ + isSendTokenInputDisabled: false, + isUserSelectionDisabled: false + }); const { createDirectMessageWithUser } = useDirect(); const { sendInviteMessage } = useSendInviteMessage(); @@ -100,7 +106,7 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp setNote('send token'); setUserSearchError(''); setError(''); - setIsInputDisabled(false); + setSendTokenInputsState({ isSendTokenInputDisabled: false, isUserSelectionDisabled: false }); dispatch(giveCoffeeActions.setShowModalSendToken(false)); }; @@ -133,7 +139,7 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp const tokenEvent: ApiTokenSentEvent = { sender_id: myProfile.userId as string, sender_name: myProfile?.userProfile?.user?.username as string, - receiver_id: infoSendToken?.receiver_id ?? userId, + receiver_id: userId, amount: token, note: note, extra_attribute: infoSendToken?.extra_attribute ?? extraAttribute @@ -142,7 +148,7 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp try { await dispatch(giveCoffeeActions.sendToken(tokenEvent)).unwrap(); dispatch(giveCoffeeActions.setSendTokenEvent({ tokenEvent: tokenEvent, status: TOKEN_SUCCESS_STATUS })); - await sendNotificationMessage(infoSendToken?.receiver_id ?? userId, token, note ?? ''); + await sendNotificationMessage(userId, token, note ?? ''); } catch (err) { dispatch(giveCoffeeActions.setSendTokenEvent({ tokenEvent: tokenEvent, status: TOKEN_FAILED_STATUS })); } @@ -180,7 +186,10 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp setSelectedUserId(infoSendToken.receiver_id ?? ''); setNote(infoSendToken.note ?? 'send token'); setExtraAttribute(infoSendToken.extra_attribute ?? ''); - setIsInputDisabled(infoSendToken.amount !== 0); + setSendTokenInputsState({ + isSendTokenInputDisabled: infoSendToken.amount !== 0, + isUserSelectionDisabled: infoSendToken.receiver_id !== '' + }); const timer = setTimeout(() => { handleClosePopup(); }, 10000); @@ -260,7 +269,7 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp userSearchError={userSearchError} userId={myProfile.userId as string} note={note} - isInputDisabled={isInputDisabled} + sendTokenInputsState={sendTokenInputsState} /> )} diff --git a/libs/components/src/lib/components/MarkdownFormatText/ModalUnknowChannel.tsx b/libs/components/src/lib/components/MarkdownFormatText/ModalUnknowChannel.tsx index 509b380f91..6fe9bb1111 100644 --- a/libs/components/src/lib/components/MarkdownFormatText/ModalUnknowChannel.tsx +++ b/libs/components/src/lib/components/MarkdownFormatText/ModalUnknowChannel.tsx @@ -1,58 +1,63 @@ -import { FRIEND_PAGE_LINK, toChannelPage, toMembersPage } from '@mezon/core'; +import { FRIEND_PAGE_LINK, toChannelPage, useAppNavigation } from '@mezon/core'; import { RootState, getStoreAsync, selectCurrentClanId, selectWelcomeChannelByClanId, toastActions } from '@mezon/store'; import { Icons } from '@mezon/ui'; import { useDispatch } from 'react-redux'; -import { useNavigate } from 'react-router-dom'; type ModalUnknowChannelProps = { - onClose: () => void; + onClose?: () => void; isError?: boolean; + errMessage?: string; + idErr?: string; }; function ModalUnknowChannel(props: ModalUnknowChannelProps) { const dispatch = useDispatch(); - const { onClose, isError = false } = props; - const navigate = useNavigate(); - - const resetErrorToastStatus = () => { - dispatch(toastActions.setErrorToastStatus(false)); + const { onClose, isError = false, errMessage, idErr } = props; + const { toClanPage, navigate } = useAppNavigation(); + const removeToastError = () => { + if (idErr) { + dispatch(toastActions.removeToastError(idErr)); + } + }; + const clearAllToastError = () => { + dispatch(toastActions.clearAllToastErrors()); }; const directToWelcomeChannel = async () => { - resetErrorToastStatus(); - + clearAllToastError(); const store = await getStoreAsync(); const currentClanId = selectCurrentClanId(store.getState() as RootState); - if (!currentClanId) { + if (!currentClanId || currentClanId === '0') { navigate(FRIEND_PAGE_LINK); return; } const welcomeChannelId = selectWelcomeChannelByClanId(store.getState(), currentClanId); if (welcomeChannelId) { + navigate(toClanPage(currentClanId)); navigate(toChannelPage(welcomeChannelId, currentClanId)); return; } - - navigate(toMembersPage(currentClanId)); }; const onCloseAndReset = () => { - onClose(); if (isError) { - resetErrorToastStatus(); + removeToastError(); } + onClose?.(); }; return ( -
+
-
+
{isError ? ( -

Oops! Something Went Wrong

+
+ {errMessage ? errMessage : 'Oops! Something Went Wrong'} +
) : ( <>

You don't have access to this link.

diff --git a/libs/components/src/lib/components/ModalListClans/SidebarLogoItem.tsx b/libs/components/src/lib/components/ModalListClans/SidebarLogoItem.tsx index 834173879a..f014dda24a 100644 --- a/libs/components/src/lib/components/ModalListClans/SidebarLogoItem.tsx +++ b/libs/components/src/lib/components/ModalListClans/SidebarLogoItem.tsx @@ -51,6 +51,7 @@ const SidebarLogoItem = () => { openRightClickModal(); }; const handleClickToJoinClan = () => { + console.log('handleClickToJoinClan'); dispatch(clansActions.joinClan({ clanId: '0' })); }; const { quantityPendingRequest } = useFriends(); diff --git a/libs/components/src/lib/components/ModalUserProfile/StatusProfile/ModalSendToken.tsx b/libs/components/src/lib/components/ModalUserProfile/StatusProfile/ModalSendToken.tsx index 00b4d56f6a..f80eeeedbc 100644 --- a/libs/components/src/lib/components/ModalUserProfile/StatusProfile/ModalSendToken.tsx +++ b/libs/components/src/lib/components/ModalUserProfile/StatusProfile/ModalSendToken.tsx @@ -21,7 +21,10 @@ type ModalSendTokenProps = { userId: string; selectedUserId: string; note: string; - isInputDisabled: boolean; + sendTokenInputsState: { + isSendTokenInputDisabled: boolean; + isUserSelectionDisabled: boolean; + }; }; const ModalSendToken = ({ @@ -37,7 +40,7 @@ const ModalSendToken = ({ userId, selectedUserId, note, - isInputDisabled + sendTokenInputsState }: ModalSendTokenProps) => { const usersClan = useSelector(selectAllUserClans); const dmGroupChatList = useSelector(selectAllDirectMessages); @@ -160,7 +163,7 @@ const ModalSendToken = ({ value={searchTerm} onClick={() => setIsDropdownOpen(true)} onChange={handleChangeSearchTerm} - disabled={isInputDisabled} + disabled={sendTokenInputsState.isUserSelectionDisabled} /> {isDropdownOpen && (
{error &&

{error}

}
diff --git a/libs/core/src/lib/chat/contexts/ChatContext.tsx b/libs/core/src/lib/chat/contexts/ChatContext.tsx index fb37bc0aa1..55254d4cd4 100644 --- a/libs/core/src/lib/chat/contexts/ChatContext.tsx +++ b/libs/core/src/lib/chat/contexts/ChatContext.tsx @@ -675,7 +675,10 @@ const ChatContextProvider: React.FC = ({ children }) = dispatch(threadsActions.add(thread)); const store = await getStoreAsync(); const allThreads = selectAllThreads(store.getState()); - const defaultThreadList: ApiChannelDescription[] = [thread as ApiChannelDescription, ...((allThreads || []) as ApiChannelDescription[])]; + const defaultThreadList: ApiChannelDescription[] = [ + thread as ApiChannelDescription, + ...((allThreads || []) as ApiChannelDescription[]) + ]; dispatch( threadsActions.updateCacheOnThreadCreation({ clanId: channel.clan_id || '', diff --git a/libs/store/src/lib/auth/auth.slice.ts b/libs/store/src/lib/auth/auth.slice.ts index 247f8dd3de..8a4360354b 100644 --- a/libs/store/src/lib/auth/auth.slice.ts +++ b/libs/store/src/lib/auth/auth.slice.ts @@ -2,6 +2,7 @@ import { LoadingStatus } from '@mezon/utils'; import { createAsyncThunk, createSelector, createSlice } from '@reduxjs/toolkit'; import { Session } from 'mezon-js'; import { ensureClientAsync, getMezonCtx, restoreLocalStorage } from '../helpers'; +import { clearAllMemoizedFunctions } from '../memoize'; export const AUTH_FEATURE_KEY = 'auth'; export interface AuthState { @@ -95,6 +96,7 @@ export const logOut = createAsyncThunk('auth/logOut', async (_, thunkAPI) => { const mezon = getMezonCtx(thunkAPI); await mezon?.logOutMezon(); thunkAPI.dispatch(authActions.setLogout()); + clearAllMemoizedFunctions(); restoreLocalStorage(['persist:auth', 'persist:apps', 'persist:categories']); }); diff --git a/libs/store/src/lib/direct/directmeta.slice.ts b/libs/store/src/lib/direct/directmeta.slice.ts index 5e8ca1cdce..90a5f0c4f2 100644 --- a/libs/store/src/lib/direct/directmeta.slice.ts +++ b/libs/store/src/lib/direct/directmeta.slice.ts @@ -116,7 +116,7 @@ export const directMetaSlice = createSlice({ sender_id: payload.sender_id, timestamp_seconds: timestamp }, - count_mess_unread : 0 + count_mess_unread: 0 } }); }, diff --git a/libs/store/src/lib/errors/errors.listener.ts b/libs/store/src/lib/errors/errors.listener.ts index 6a9abbb643..e2671724d4 100644 --- a/libs/store/src/lib/errors/errors.listener.ts +++ b/libs/store/src/lib/errors/errors.listener.ts @@ -124,7 +124,11 @@ errorListenerMiddleware.startListening({ return; } if (toast.type === 'error') { - listenerApi.dispatch(toastActions.setErrorToastStatus(true)); + listenerApi.dispatch( + toastActions.addToastError({ + message: toast.message as string + }) + ); } } }); diff --git a/libs/store/src/lib/toasts/toasts.slice.ts b/libs/store/src/lib/toasts/toasts.slice.ts index 61d4a3c5d7..d17cd49e2e 100644 --- a/libs/store/src/lib/toasts/toasts.slice.ts +++ b/libs/store/src/lib/toasts/toasts.slice.ts @@ -9,7 +9,7 @@ const toastsAdapter = createEntityAdapter(); const initialState = { ...toastsAdapter.getInitialState(), - toastErrorStatus: false + toastErrors: [] as { id: string; message: string }[] }; const addToast = createAsyncThunk( 'toasts/addToast', @@ -62,27 +62,38 @@ export const toastsSlice = createSlice({ clearToasts: (state) => { toastsAdapter.removeAll(state); }, - setErrorToastStatus: (state, action: PayloadAction) => { - state.toastErrorStatus = action.payload; + addToastError: (state, action: PayloadAction<{ message?: string }>) => { + const message = action.payload.message; + if (!message || state.toastErrors.find((error) => error.message === message)) { + return; + } + const id = Date.now().toString(); + state.toastErrors.push({ id, message }); + }, + removeToastError: (state, action: PayloadAction) => { + state.toastErrors = state.toastErrors.filter((error) => error.id !== action.payload); + }, + clearAllToastErrors: (state) => { + state.toastErrors = []; } } }); -export const { addOneToast, removeToast, clearToasts, setErrorToastStatus } = toastsSlice.actions; +export const { addOneToast, removeToast, clearToasts, addToastError, removeToastError, clearAllToastErrors } = toastsSlice.actions; export const toastActions = { addToast, removeToast, clearToasts, - setErrorToastStatus + addToastError, + removeToastError, + clearAllToastErrors }; // Create selectors using the adapter's getSelectors method export const { selectAll: selectToasts, selectById: selectToastById } = toastsAdapter.getSelectors( (state: { toasts: typeof initialState }) => state.toasts ); -export const selectToastErrorStatus = createSelector( - [(state: { toasts: typeof initialState }) => state.toasts], - (toastsState) => toastsState.toastErrorStatus -); +export const selectToastErrors = createSelector([(state: { toasts: typeof initialState }) => state.toasts], (toastsState) => toastsState.toastErrors); + export const toastsReducer = toastsSlice.reducer; diff --git a/libs/transport/src/lib/contexts/MezonContext.tsx b/libs/transport/src/lib/contexts/MezonContext.tsx index 847a7b7ac0..26498cb0a9 100644 --- a/libs/transport/src/lib/contexts/MezonContext.tsx +++ b/libs/transport/src/lib/contexts/MezonContext.tsx @@ -75,6 +75,8 @@ const MezonContextProvider: React.FC = ({ children, m throw new Error('Mezon client not initialized'); } const session = await clientRef.current.checkLoginRequest(LoginRequest); + const socket = await createSocket(); + socketRef.current = socket; return session; }, []);