Skip to content
5 changes: 4 additions & 1 deletion apps/chat/src/app/pages/channel/ChannelVoice.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
handleParticipantMeetState,
selectCurrentChannel,
selectCurrentClan,
selectIsShowSettingFooter,
selectShowCamera,
selectShowMicrophone,
selectVoiceFullScreen,
Expand Down Expand Up @@ -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 (
<div
className={`${!isChannelMezonVoice ? 'hidden' : ''} absolute ${isWindowsDesktop || isLinuxDesktop ? 'bottom-[21px]' : 'bottom-0'} right-0 z-30`}
className={`${!isChannelMezonVoice || isShowSettingFooter.status ? 'hidden' : ''} absolute ${isWindowsDesktop || isLinuxDesktop ? 'bottom-[21px]' : 'bottom-0'} right-0 z-30`}
style={{ width: 'calc(100% - 72px - 272px)', height: isWindowsDesktop || isLinuxDesktop ? 'calc(100% - 21px)' : '100%' }}
>
{tokenRef.current === '' || !serverUrl ? (
Expand Down
90 changes: 43 additions & 47 deletions apps/chat/src/app/pages/main/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ import {
selectSignalingDataByUserId,
selectStatusMenu,
selectTheme,
selectToastErrorStatus,
selectToastErrors,
useAppDispatch,
useAppSelector
} from '@mezon/store';
Expand Down Expand Up @@ -276,56 +276,52 @@ function MyApp() {
const handleClose = () => {
dispatch(e2eeActions.setOpenModalE2ee(false));
};
// show toast error
const toastErrorStatus = useSelector(selectToastErrorStatus);
const [openUnknown, closeUnknown] = useModal(() => {
return <ModalUnknowChannel isError={true} onClose={closeUnknown} />;
}, []);
useEffect(() => {
if (toastErrorStatus) {
openUnknown();
} else {
closeUnknown();
}
}, [toastErrorStatus]);

const toastError = useSelector(selectToastErrors);

return (
<div
className={`flex h-screen min-[480px]:pl-[72px] ${closeMenu ? (statusMenu ? 'pl-[72px]' : '') : ''} overflow-hidden text-gray-100 relative dark:bg-bgPrimary bg-bgLightModeSecond`}
onClick={handleClick}
>
{previewMode && <PreviewOnboardingMode />}
{openPopupForward && <ForwardMessageModal openModal={openPopupForward} />}
<SidebarMenu openCreateClanModal={openCreateClanModal} />
<MainContent />
<>
{toastError.map((error) => (
<ModalUnknowChannel key={error.id} isError={true} errMessage={error.message} idErr={error.id} />
))}
<div
className={`fixed ${isWindowsDesktop || isLinuxDesktop ? 'h-heightTitleBarWithoutTopBar' : 'h-heightWithoutTopBar'} bottom-0 ${closeMenu ? (statusMenu ? 'hidden' : 'w-full') : isShowChatStream ? 'max-sm:hidden' : 'w-full'} ${currentChannel?.type === ChannelType.CHANNEL_TYPE_STREAMING && currentClanId !== '0' && memberPath !== currentURL ? 'flex flex-1 justify-center items-center' : 'hidden pointer-events-none'}`}
style={streamStyle}
className={`flex h-screen min-[480px]:pl-[72px] ${closeMenu ? (statusMenu ? 'pl-[72px]' : '') : ''} overflow-hidden text-gray-100 relative dark:bg-bgPrimary bg-bgLightModeSecond`}
onClick={handleClick}
>
<ChannelStream
key={currentStreamInfo?.streamId}
currentChannel={currentChannel}
currentStreamInfo={currentStreamInfo}
handleChannelClick={handleChannelClick}
streamVideoRef={streamVideoRef}
disconnect={disconnect}
isStream={isStream}
/>
</div>
{previewMode && <PreviewOnboardingMode />}
{openPopupForward && <ForwardMessageModal openModal={openPopupForward} />}
<SidebarMenu openCreateClanModal={openCreateClanModal} />
<MainContent />
<div
className={`fixed ${isWindowsDesktop || isLinuxDesktop ? 'h-heightTitleBarWithoutTopBar' : 'h-heightWithoutTopBar'} bottom-0 ${closeMenu ? (statusMenu ? 'hidden' : 'w-full') : isShowChatStream ? 'max-sm:hidden' : 'w-full'} ${currentChannel?.type === ChannelType.CHANNEL_TYPE_STREAMING && currentClanId !== '0' && memberPath !== currentURL ? 'flex flex-1 justify-center items-center' : 'hidden pointer-events-none'}`}
style={streamStyle}
>
<ChannelStream
key={currentStreamInfo?.streamId}
currentChannel={currentChannel}
currentStreamInfo={currentStreamInfo}
handleChannelClick={handleChannelClick}
streamVideoRef={streamVideoRef}
disconnect={disconnect}
isStream={isStream}
/>
</div>

{isPlayRingTone &&
!!dataCall &&
!isInCall &&
directId !== dataCall?.channel_id &&
dataCall?.data_type === WebrtcSignalingType.WEBRTC_SDP_OFFER && (
<ModalCall dataCall={dataCall} userId={userProfile?.user?.id || ''} triggerCall={triggerCall} />
)}

<DmCalling ref={dmCallingRef} dmGroupId={groupCallId} directId={directId || ''} />
{openModalE2ee && !hasKeyE2ee && <MultiStepModalE2ee onClose={handleClose} />}
{openModalAttachment && <MessageModalImageWrapper />}
{isShowFirstJoinPopup && <FirstJoinPopup openCreateClanModal={openCreateClanModal} onclose={() => setIsShowFirstJoinPopup(false)} />}
{isShowPopupQuickMess && <PopupQuickMess />}
</div>
{isPlayRingTone &&
!!dataCall &&
!isInCall &&
directId !== dataCall?.channel_id &&
dataCall?.data_type === WebrtcSignalingType.WEBRTC_SDP_OFFER && (
<ModalCall dataCall={dataCall} userId={userProfile?.user?.id || ''} triggerCall={triggerCall} />
)}

<DmCalling ref={dmCallingRef} dmGroupId={groupCallId} directId={directId || ''} />
{openModalE2ee && !hasKeyE2ee && <MultiStepModalE2ee onClose={handleClose} />}
{openModalAttachment && <MessageModalImageWrapper />}
{isShowFirstJoinPopup && <FirstJoinPopup openCreateClanModal={openCreateClanModal} onclose={() => setIsShowFirstJoinPopup(false)} />}
{isShowPopupQuickMess && <PopupQuickMess />}
</div>
</>
);
}

Expand Down
21 changes: 15 additions & 6 deletions libs/components/src/lib/components/FooterProfile/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,13 @@ function FooterProfile({ name, status, avatar, userId, isDM }: FooterProfileProp
const [userSearchError, setUserSearchError] = useState<string | null>(null);
const [resetTimerStatus, setResetTimerStatus] = useState<number>(0);
const [noClearStatus, setNoClearStatus] = useState<boolean>(false);
const [isInputDisabled, setIsInputDisabled] = useState<boolean>(false);
const [sendTokenInputsState, setSendTokenInputsState] = useState<{
isSendTokenInputDisabled: boolean;
isUserSelectionDisabled: boolean;
}>({
isSendTokenInputDisabled: false,
isUserSelectionDisabled: false
});

const { createDirectMessageWithUser } = useDirect();
const { sendInviteMessage } = useSendInviteMessage();
Expand Down Expand Up @@ -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));
};

Expand Down Expand Up @@ -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
Expand All @@ -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 }));
}
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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}
/>
)}
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<div className="w-[100vw] h-[100vh] overflow-hidden fixed top-0 left-0 z-50 bg-black bg-opacity-80 flex flex-row justify-center items-center">
<div className="w-[100vw] h-[100vh] overflow-hidden fixed top-0 left-0 z-50 bg-black flex flex-row justify-center items-center">
<div className="w-fit h-fit dark:bg-bgPrimary bg-bgLightModeSecond rounded-lg flex-col justify-start items-start gap-3 inline-flex overflow-hidden">
<div className="dark:text-white text-black">
<div className={`dark:text-white text-black ${isError ? 'w-[400px]' : ''} `}>
<div className="p-4 relative">
<div className="flex flex-col items-center gap-y-3 ">
<Icons.IconClockChannel />
{isError ? (
<h3 className="font-bold text-2xl dark:text-white text-black">Oops! Something Went Wrong</h3>
<h5 className="font-bold text-2xl dark:text-white text-black w-full flex justify-center">
{errMessage ? errMessage : 'Oops! Something Went Wrong'}
</h5>
) : (
<>
<h3 className="font-bold text-2xl dark:text-white text-black">You don't have access to this link.</h3>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ const SidebarLogoItem = () => {
openRightClickModal();
};
const handleClickToJoinClan = () => {
console.log('handleClickToJoinClan');
dispatch(clansActions.joinClan({ clanId: '0' }));
};
const { quantityPendingRequest } = useFriends();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ type ModalSendTokenProps = {
userId: string;
selectedUserId: string;
note: string;
isInputDisabled: boolean;
sendTokenInputsState: {
isSendTokenInputDisabled: boolean;
isUserSelectionDisabled: boolean;
};
};

const ModalSendToken = ({
Expand All @@ -37,7 +40,7 @@ const ModalSendToken = ({
userId,
selectedUserId,
note,
isInputDisabled
sendTokenInputsState
}: ModalSendTokenProps) => {
const usersClan = useSelector(selectAllUserClans);
const dmGroupChatList = useSelector(selectAllDirectMessages);
Expand Down Expand Up @@ -160,7 +163,7 @@ const ModalSendToken = ({
value={searchTerm}
onClick={() => setIsDropdownOpen(true)}
onChange={handleChangeSearchTerm}
disabled={isInputDisabled}
disabled={sendTokenInputsState.isUserSelectionDisabled}
/>
{isDropdownOpen && (
<div
Expand Down Expand Up @@ -230,7 +233,7 @@ const ModalSendToken = ({
className="dark:text-[#B5BAC1] text-textLightTheme outline-none w-full h-10 p-[10px] dark:bg-bgInputDark bg-bgLightModeThird text-base rounded placeholder:text-sm appearance-none"
placeholder="VND"
onChange={handleChangeSendToken}
disabled={isInputDisabled}
disabled={sendTokenInputsState.isSendTokenInputDisabled}
/>
{error && <p className="text-red-500 text-xs mt-1">{error}</p>}
</div>
Expand Down
5 changes: 4 additions & 1 deletion libs/core/src/lib/chat/contexts/ChatContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,10 @@ const ChatContextProvider: React.FC<ChatContextProviderProps> = ({ 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 || '',
Expand Down
2 changes: 2 additions & 0 deletions libs/store/src/lib/auth/auth.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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']);
});

Expand Down
2 changes: 1 addition & 1 deletion libs/store/src/lib/direct/directmeta.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ export const directMetaSlice = createSlice({
sender_id: payload.sender_id,
timestamp_seconds: timestamp
},
count_mess_unread : 0
count_mess_unread: 0
}
});
},
Expand Down
6 changes: 5 additions & 1 deletion libs/store/src/lib/errors/errors.listener.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
})
);
}
}
});
Loading