diff --git a/Makefile b/Makefile index 30401bb7360c..d93789640e1f 100644 --- a/Makefile +++ b/Makefile @@ -51,6 +51,10 @@ deploy-init: deploy-appbundle: cp $(BUILD_DIR)/*.min.js $(DEPLOY_DIR) -cp $(BUILD_DIR)/*.min.js.map $(DEPLOY_DIR) + @if [ -d "$(BUILD_DIR)/chunks" ]; then \ + mkdir -p $(DEPLOY_DIR)/chunks; \ + cp $(BUILD_DIR)/chunks/* $(DEPLOY_DIR)/chunks/; \ + fi deploy-lib-meet: cp \ diff --git a/conference.js b/conference.js index 876713f065ca..e627d2ba21bf 100644 --- a/conference.js +++ b/conference.js @@ -2133,7 +2133,7 @@ export default { * @param {boolean} [notifyOnConferenceTermination] whether to notify * the user on conference termination */ - hangup(requestFeedback = false, hangupReason, notifyOnConferenceTermination) { + hangup(requestFeedback = false, hangupReason, notifyOnConferenceTermination, hangupMessage) { APP.store.dispatch(disableReceiver()); this._stopProxyConnection(); @@ -2154,7 +2154,7 @@ export default { const feedbackDialogClosed = (feedbackResult = {}) => { if (!feedbackResult.wasDialogShown && hangupReason && notifyOnConferenceTermination) { return APP.store.dispatch( - openLeaveReasonDialog(hangupReason)).then(() => feedbackResult); + openLeaveReasonDialog(hangupReason, hangupMessage)).then(() => feedbackResult); } return Promise.resolve(feedbackResult); diff --git a/images/icon-small.png b/images/icon-small.png new file mode 100644 index 000000000000..ce92faab0b2c Binary files /dev/null and b/images/icon-small.png differ diff --git a/images/logo192.png b/images/logo192.png index f29327ffd6b4..6594169d50e9 100644 Binary files a/images/logo192.png and b/images/logo192.png differ diff --git a/images/logo512.png b/images/logo512.png deleted file mode 100644 index 2a7ba082dd77..000000000000 Binary files a/images/logo512.png and /dev/null differ diff --git a/index.html b/index.html index b4fb9d33ceef..c0af7eb0a937 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ - + diff --git a/lang/main-es.json b/lang/main-es.json index 0d232107973d..a58a88a1fcb6 100644 --- a/lang/main-es.json +++ b/lang/main-es.json @@ -321,6 +321,8 @@ "enterDisplayName": "Por favor ingresa tu nombre aquí", "error": "Error", "errorJoiningMeeting": "Error al unirse a la reunión", + "errorJoiningMeetingRoomClosedTitle": "La sala está cerrada", + "errorJoiningMeetingRoomClosedMsg": "Esta reunión está actualmente cerrada. Por favor, espera a que el organizador reabra la sala antes de unirte.", "gracefulShutdown": "Nuestro servicio se encuentra en mantenimiento. Por favor, intente más tarde.", "grantModeratorDialog": "¿Estás seguro de que quieres convertir a este participante en moderador?", "grantModeratorTitle": "Convertir en moderador", @@ -330,6 +332,8 @@ "incorrectRoomLockPassword": "Contraseña incorrecta", "internalError": "¡Ups! Algo salió mal. El siguiente error ocurrió: {{error}}", "internalErrorTitle": "Error interno", + "kickDuplicateTitle": "Oops! Ya estas en la reunión ", + "kickDuplicateMessage": "Parece que ya estabas en la reunión. Prueba a mirar en otra ventana, quiza ya tengas la reunion iniciada.", "kickMessage": "Puede ponerse en contacto con {{participantDisplayName}} para obtener más detalles.", "kickParticipantButton": "Expulsar", "kickParticipantDialog": "¿Seguro que quiere expulsar a este participante?", diff --git a/lang/main.json b/lang/main.json index b9ce97430f15..95f22012d20f 100644 --- a/lang/main.json +++ b/lang/main.json @@ -366,6 +366,8 @@ "enterDisplayName": "Enter your name", "error": "Error", "errorJoiningMeeting": "Error joining meeting", + "errorJoiningMeetingRoomClosedTitle": "Room is closed", + "errorJoiningMeetingRoomClosedMsg": "This meeting room is currently closed. Please wait for the organizer to reopen the room before joining.", "errorRoomCreationRestriction": "You tried to join too quickly, please come back in a bit.", "gracefulShutdown": "Our service is currently down for maintenance. Please try again later.", "grantModeratorDialog": "Are you sure you want to grant moderator rights to {{participantName}}?", @@ -376,6 +378,8 @@ "incorrectRoomLockPassword": "Incorrect password", "internalError": "Oops! Something went wrong. The following error occurred: {{error}}", "internalErrorTitle": "Internal error", + "kickDuplicateTitle": "Oops! You are already in the meeting", + "kickDuplicateMessage": "It looks like you already joined this meeting. Try looking into other open windows where you might have the meeting running", "kickMessage": "You can contact {{participantDisplayName}} for more details.", "kickParticipantButton": "Kick", "kickParticipantDialog": "Are you sure you want to kick this participant?", diff --git a/package.json b/package.json index 083d0da4e9f9..d0d50e929a5c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "@internxt/css-config": "^1.1.0", "@internxt/eslint-config-internxt": "^2.0.1", "@internxt/lib": "^1.4.1", - "@internxt/sdk": "1.16.3", + "@internxt/sdk": "1.17.0", "@internxt/ui": "0.1.15", "@jitsi/excalidraw": "https://github.com/jitsi/excalidraw/releases/download/0.18.5/jitsi-excalidraw-v0.18.5.tgz", "@jitsi/js-utils": "2.6.7", @@ -85,7 +85,7 @@ "js-md5": "0.6.1", "js-sha512": "0.8.0", "jwt-decode": "2.2.0", - "lib-meet": "https://github.com/internxt/lib-meet/releases/download/v.1.0.0/lib-meet-1.0.0.tgz", + "lib-meet": "https://github.com/internxt/lib-meet/releases/download/v.1.0.1/lib-meet-1.0.1.tgz", "lodash-es": "4.18.1", "moment": "2.29.4", "moment-duration-format": "2.2.2", diff --git a/react/features/base/connection/actions.any.ts b/react/features/base/connection/actions.any.ts index d3f0349c48ab..54c532138f93 100644 --- a/react/features/base/connection/actions.any.ts +++ b/react/features/base/connection/actions.any.ts @@ -247,15 +247,17 @@ export function _connectInternal({ let userUUID: string | undefined; if (isAnonymous) { - userUUID = SessionStorageManager.instance.getOrCreateAnonymousUUID(); + userUUID = SessionStorageManager.instance.getOrCreateUserID(); } - const { token: jwt, appId } = await MeetingService.instance.joinCall(room, { + const { token: jwt, appId, userId } = await MeetingService.instance.joinCall(room, { name: displayName ?? name ?? "", lastname: lastname ?? "", anonymous: !!isAnonymous, anonymousId: userUUID, }); + SessionStorageManager.instance.setUserID(userId); + const newOptions = get8x8Options(options, appId, room); const connection = new JitsiMeetJS.JitsiConnection(appId, jwt, newOptions); @@ -405,17 +407,22 @@ export function _connectInternal({ }); }); } catch (error: Error | any) { - const errorMessage = error?.message || "Failed to join the meeting"; + let errorMessage = error?.message || "Failed to join the meeting"; + let errorTitle = errorMessage; + if (error?.status === 403) { + errorMessage = "dialog.errorJoiningMeetingRoomClosedMsg"; + errorTitle = "dialog.errorJoiningMeetingRoomClosedTitle"; + } - dispatch(setJoinRoomError(true, errorMessage)); + dispatch(setJoinRoomError(true, errorTitle)); dispatch( showErrorNotification( { - titleKey: "dialog.errorJoiningMeeting", + titleKey: errorTitle, descriptionKey: errorMessage, hideErrorSupportLink: true, }, - NOTIFICATION_TIMEOUT_TYPE.LONG + NOTIFICATION_TIMEOUT_TYPE.STICKY ) ); diff --git a/react/features/base/connection/actions.web.ts b/react/features/base/connection/actions.web.ts index b35376d4e6c2..aa87312ecf58 100644 --- a/react/features/base/connection/actions.web.ts +++ b/react/features/base/connection/actions.web.ts @@ -23,12 +23,10 @@ import { SessionStorageManager } from '../meet/SessionStorageManager'; * @returns {Promise} */ export async function leaveCallWithUserIdentification(roomId: string): Promise { - const user = LocalStorageManager.instance.getUser(); - let payload = undefined; - if (!user){ - payload = { userId: SessionStorageManager.instance.getAnonymousUUID() || '' }; + const userId = SessionStorageManager.instance.getUserID(); + if (userId) { + return await MeetingService.instance.leaveCall(roomId, { userId }); } - return await MeetingService.instance.leaveCall(roomId, payload); } export * from "./actions.any"; @@ -105,7 +103,7 @@ export function connect(id?: string, password?: string) { * the user on conference termination. * @returns {Function} */ -export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean) { +export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: string, notifyOnConferenceTermination?: boolean, feedbackMessage?: string) { // XXX For web based version we use conference hanging up logic from the old app. return async (dispatch: IStore["dispatch"]) => { if (LocalRecordingManager.isRecordingLocally()) { @@ -132,7 +130,7 @@ export function hangup(requestFeedback = false, roomId?: string, feedbackTitle?: await leaveCallWithUserIdentification(roomId); - return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination); + return APP.conference.hangup(requestFeedback, feedbackTitle, notifyOnConferenceTermination, feedbackMessage); }; } diff --git a/react/features/base/meet/LocalStorageManager.test.ts b/react/features/base/meet/LocalStorageManager.test.ts new file mode 100644 index 000000000000..388206706f30 --- /dev/null +++ b/react/features/base/meet/LocalStorageManager.test.ts @@ -0,0 +1,46 @@ +import { beforeEach, describe, expect, test, vi } from "vitest"; +import { LocalStorageManager } from "./LocalStorageManager"; + +const MOCK_DISPlAY_NAME = "mock-display-name"; +const key = (LocalStorageManager as any)['KEYS']?.DISPLAY_NAME; + +describe("LocalStorageManager tests", () => { + beforeEach(() => { + localStorage.clear(); + vi.clearAllMocks(); + }); + + test("when local storage is called repeatedly, then the same instance is returned", () => { + const a = LocalStorageManager.instance; + const b = LocalStorageManager.instance; + expect(a).toBe(b); + }); + + test("when no display name is stored, then returns underfined", () => { + expect(LocalStorageManager.instance.getDisplayName()).toBeUndefined(); + }); + + test("when display name is stored, then returns it", () => { + LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME); + expect(LocalStorageManager.instance.getDisplayName()).toBe(MOCK_DISPlAY_NAME); + }); + + test("when the stored display name was modified, then returns the last modification", () => { + LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME); + const modifiedName = 'new-display-name'; + localStorage.setItem(key, modifiedName); + expect(LocalStorageManager.instance.getDisplayName()).toBe(modifiedName); + }); + + test("when display name is saved in the local storage, then it does not bleed into the session storage", () => { + LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME); + expect(sessionStorage.getItem(key)).toBeNull(); + }); + + test("when local storage is cleaned, then display name becomes underfined", () => { + LocalStorageManager.instance.setDisplayName(MOCK_DISPlAY_NAME); + LocalStorageManager.instance.clearCredentials(); + expect(LocalStorageManager.instance.getDisplayName()).toBeUndefined(); + }); + +}); \ No newline at end of file diff --git a/react/features/base/meet/LocalStorageManager.ts b/react/features/base/meet/LocalStorageManager.ts index 808fc0ffca5f..9fad106fc3f3 100644 --- a/react/features/base/meet/LocalStorageManager.ts +++ b/react/features/base/meet/LocalStorageManager.ts @@ -18,6 +18,7 @@ export class LocalStorageManager { MNEMONIC: "xMnemonic", USER: "xUser", SUBSCRIPTION: "xSubscription", + DISPLAY_NAME: "xMeetDisplayName", }; private constructor() {} @@ -169,6 +170,19 @@ export class LocalStorageManager { this.remove(LocalStorageManager.KEYS.SUBSCRIPTION); } + /** + * Gets the user display name + */ + public getDisplayName(): string | null | undefined { + return this.get(LocalStorageManager.KEYS.DISPLAY_NAME); + } + + /** + * Sets the user display name + */ + public setDisplayName(displayName: string): void { + this.set(LocalStorageManager.KEYS.DISPLAY_NAME, displayName); + } /** * Saves the session credentials @@ -195,6 +209,7 @@ export class LocalStorageManager { this.remove(LocalStorageManager.KEYS.MNEMONIC); this.remove(LocalStorageManager.KEYS.USER); this.remove(LocalStorageManager.KEYS.SUBSCRIPTION); + this.remove(LocalStorageManager.KEYS.DISPLAY_NAME); } public clearStorage(): void { diff --git a/react/features/base/meet/SessionStorageManager.test.ts b/react/features/base/meet/SessionStorageManager.test.ts index bbfb5d8ed4ec..55633c8daeae 100644 --- a/react/features/base/meet/SessionStorageManager.test.ts +++ b/react/features/base/meet/SessionStorageManager.test.ts @@ -18,16 +18,16 @@ describe("SessionStorageManager tests", () => { }); it("returns null when no anonymous UUID is stored", () => { - expect(SessionStorageManager.instance.getAnonymousUUID()).toBeNull(); + expect(SessionStorageManager.instance.getUserID()).toBeNull(); }); it("returns the stored anonymous UUID when one exists", () => { sessionStorage.setItem(ANON_UUID_KEY, "existing-uuid"); - expect(SessionStorageManager.instance.getAnonymousUUID()).toBe("existing-uuid"); + expect(SessionStorageManager.instance.getUserID()).toBe("existing-uuid"); }); it("generates and stores a UUID when none exists", () => { - const result = SessionStorageManager.instance.getOrCreateAnonymousUUID(); + const result = SessionStorageManager.instance.getOrCreateUserID(); expect(result).toBe(MOCK_UUID); expect(sessionStorage.getItem(ANON_UUID_KEY)).toBe(MOCK_UUID); }); @@ -35,14 +35,14 @@ describe("SessionStorageManager tests", () => { it("returns existing UUID without generating a new one", async () => { sessionStorage.setItem(ANON_UUID_KEY, "pre-existing-uuid"); - const result = SessionStorageManager.instance.getOrCreateAnonymousUUID(); + const result = SessionStorageManager.instance.getOrCreateUserID(); expect(result).toBe("pre-existing-uuid"); expect(v4).not.toHaveBeenCalled(); }); it("does not bleed into localStorage", () => { - SessionStorageManager.instance.getOrCreateAnonymousUUID(); + SessionStorageManager.instance.getOrCreateUserID(); expect(localStorage.getItem(ANON_UUID_KEY)).toBeNull(); }); diff --git a/react/features/base/meet/SessionStorageManager.ts b/react/features/base/meet/SessionStorageManager.ts index 7f9d02c0c1d2..ebccd62cb3b0 100644 --- a/react/features/base/meet/SessionStorageManager.ts +++ b/react/features/base/meet/SessionStorageManager.ts @@ -1,6 +1,6 @@ import { v4 } from "uuid"; -export const ANON_UUID_KEY = "xAnonymousUserUUID"; +export const ANON_UUID_KEY = "xMeetUserUUID"; export class SessionStorageManager { private static _instance: SessionStorageManager; @@ -14,20 +14,25 @@ export class SessionStorageManager { return SessionStorageManager._instance; } - public getOrCreateAnonymousUUID(): string { - let uuid = this.getAnonymousUUID(); + public getOrCreateUserID(): string { + let uuid = this.getUserID(); if (!uuid) { uuid = v4(); - sessionStorage.setItem(ANON_UUID_KEY, uuid); + this.setUserID(uuid); } return uuid; } - public getAnonymousUUID(): string | null { + public getUserID(): string | null { return sessionStorage.getItem(ANON_UUID_KEY); } + public setUserID(userId: string): void { + sessionStorage.setItem(ANON_UUID_KEY, userId); + } + + } export default SessionStorageManager.instance; \ No newline at end of file diff --git a/react/features/base/meet/general/components/ErrorMessage.tsx b/react/features/base/meet/general/components/ErrorMessage.tsx index febb9ede52a1..4e69acf5510d 100644 --- a/react/features/base/meet/general/components/ErrorMessage.tsx +++ b/react/features/base/meet/general/components/ErrorMessage.tsx @@ -1,4 +1,4 @@ -import { WarningCircle } from "@phosphor-icons/react"; +import { WarningCircleIcon } from "@phosphor-icons/react"; import React from "react"; interface ErrorMessageProps { @@ -8,7 +8,7 @@ interface ErrorMessageProps { export const ErrorMessage: React.FC = ({ message }) => (
- +
{message}
diff --git a/react/features/base/meet/general/components/MediaControls.tsx b/react/features/base/meet/general/components/MediaControls.tsx index 782d9b644468..fe936a0b2060 100644 --- a/react/features/base/meet/general/components/MediaControls.tsx +++ b/react/features/base/meet/general/components/MediaControls.tsx @@ -1,5 +1,11 @@ import { CircleButton } from "@internxt/ui"; -import { ExclamationMark, Microphone, MicrophoneSlash, VideoCamera, VideoCameraSlash } from "@phosphor-icons/react"; +import { + ExclamationMarkIcon, + MicrophoneIcon, + MicrophoneSlashIcon, + VideoCameraIcon, + VideoCameraSlashIcon +} from "@phosphor-icons/react"; import React, { useState } from "react"; import MeetAudioSettingsPopUp from "../containers/MeetAudioSettingsPopup"; @@ -16,7 +22,7 @@ interface MediaControlsProps { onAudioOptionsClick: () => void; } const indicatorProps = { - icon: , + icon: , className: "bg-orange", }; const MediaControls: React.FC = ({ @@ -54,9 +60,9 @@ const MediaControls: React.FC = ({ }} > {hasVideoPermissions && !isVideoMuted ? ( - + ) : ( - + )} = ({ }} > {isAudioMuted || !hasAudioPermissions ? ( - + ) : ( - + )} diff --git a/react/features/base/meet/general/components/PermissionModal.tsx b/react/features/base/meet/general/components/PermissionModal.tsx index b37e0f4ea4fa..2270b88fcd9b 100644 --- a/react/features/base/meet/general/components/PermissionModal.tsx +++ b/react/features/base/meet/general/components/PermissionModal.tsx @@ -1,7 +1,7 @@ import { TransparentModal } from "@internxt/ui"; -import { Microphone, VideoCamera } from "@phosphor-icons/react"; +import { MicrophoneIcon, VideoCameraIcon } from "@phosphor-icons/react"; import React from "react"; -import ReactDOM from "react-dom"; +import { createPortal } from "react-dom"; export interface PermissionModalProps { translate: (key: string) => string; @@ -30,10 +30,10 @@ const PermissionModal = ({
- +
- +

); - return ReactDOM.createPortal(modalContent, bodyElement); + return createPortal(modalContent, bodyElement); }; export default PermissionModal; diff --git a/react/features/base/meet/services/__tests__/meeting.service.test.ts b/react/features/base/meet/services/__tests__/meeting.service.test.ts index 20c5798f3d91..a6ed1facfb02 100644 --- a/react/features/base/meet/services/__tests__/meeting.service.test.ts +++ b/react/features/base/meet/services/__tests__/meeting.service.test.ts @@ -185,6 +185,7 @@ describe("MeetingService", () => { describe("leaveCall", () => { it("When leaving a call with valid ID, then the operation completes successfully", async () => { const mockCallId = "call-123"; + const mockPayload: LeaveCallPayload = { userId: "anon-uuid-789" }; const mockLeaveCallResponse = { success: true }; const mockMeetClient = { @@ -193,14 +194,14 @@ describe("MeetingService", () => { mockedGetMeet.mockReturnValue(mockMeetClient); - const result = await MeetingService.instance.leaveCall(mockCallId); + const result = await MeetingService.instance.leaveCall(mockCallId, mockPayload); expect(mockedGetMeet).toHaveBeenCalledTimes(1); expect(mockedGetMeet).toHaveBeenCalledWith(); expect(mockMeetClient.leaveCall).toHaveBeenCalledTimes(1); - expect(mockMeetClient.leaveCall).toHaveBeenCalledWith(mockCallId, undefined); + expect(mockMeetClient.leaveCall).toHaveBeenCalledWith(mockCallId, mockPayload); expect(mockMeetClient.leaveCall.mock.calls[0][0]).toBe(mockCallId); - expect(mockMeetClient.leaveCall.mock.calls[0][1]).toBeUndefined(); + expect(mockMeetClient.leaveCall.mock.calls[0][1]).toEqual(mockPayload); expect(result).toEqual(mockLeaveCallResponse); }); @@ -225,6 +226,7 @@ describe("MeetingService", () => { it("When leaving a call fails, then an error is thrown", async () => { const mockCallId = "call-123"; + const mockPayload: LeaveCallPayload = { userId: "anon-uuid-789" }; const mockError = new Error("Failed to leave call"); const mockMeetClient = { @@ -233,11 +235,11 @@ describe("MeetingService", () => { mockedGetMeet.mockReturnValue(mockMeetClient); - await expect(MeetingService.instance.leaveCall(mockCallId)).rejects.toThrow(mockError); + await expect(MeetingService.instance.leaveCall(mockCallId, mockPayload)).rejects.toThrow(mockError); expect(mockedGetMeet).toHaveBeenCalledTimes(1); expect(mockedGetMeet).toHaveBeenCalledWith(); expect(mockMeetClient.leaveCall).toHaveBeenCalledTimes(1); - expect(mockMeetClient.leaveCall).toHaveBeenCalledWith(mockCallId, undefined); + expect(mockMeetClient.leaveCall).toHaveBeenCalledWith(mockCallId, mockPayload); expect(mockMeetClient.leaveCall.mock.calls[0][0]).toBe(mockCallId); }); }); diff --git a/react/features/base/meet/services/meeting.service.ts b/react/features/base/meet/services/meeting.service.ts index 4e643b3e0f74..bac11b1225fc 100644 --- a/react/features/base/meet/services/meeting.service.ts +++ b/react/features/base/meet/services/meeting.service.ts @@ -34,7 +34,7 @@ class MeetingService { return await meetClient.joinCall(callId, payload); }; - public leaveCall = async (callId: string, payload?: LeaveCallPayload): Promise => { + public leaveCall = async (callId: string, payload: LeaveCallPayload): Promise => { const meetClient = SdkManager.instance.getMeet(); return await meetClient.leaveCall(callId, payload); }; diff --git a/react/features/base/meet/views/Conference/components/Header.tsx b/react/features/base/meet/views/Conference/components/Header.tsx index c8cad049a39e..c4e348851cf8 100644 --- a/react/features/base/meet/views/Conference/components/Header.tsx +++ b/react/features/base/meet/views/Conference/components/Header.tsx @@ -1,5 +1,5 @@ import { Header as IntxHeader } from "@internxt/ui"; -import { SquaresFour, UserFocus } from "@phosphor-icons/react"; +import { SquaresFourIcon, UserFocusIcon } from "@phosphor-icons/react"; import React from "react"; import ConferenceTimer from "./ConferenceTimer"; @@ -47,10 +47,10 @@ const RightContent = React.memo(({ mode, translate, onSetModeClicked }: RightCon className="flex flex-row justify-center items-center space-x-2 p-[10px] bg-black/50 border border-white/10 rounded-2xl" > {mode === "gallery" ? <> - {translate("meet.meeting.button.gallery")} + {translate("meet.meeting.button.gallery")} : <> - {translate("meet.meeting.button.speaker")} + {translate("meet.meeting.button.speaker")} } ); diff --git a/react/features/base/meet/views/Conference/components/InviteUserModal.tsx b/react/features/base/meet/views/Conference/components/InviteUserModal.tsx index b7cbe4c92371..d0bfdf916c8d 100644 --- a/react/features/base/meet/views/Conference/components/InviteUserModal.tsx +++ b/react/features/base/meet/views/Conference/components/InviteUserModal.tsx @@ -1,5 +1,5 @@ import { Button, TransparentModal } from "@internxt/ui"; -import { Check, Link, X } from "@phosphor-icons/react"; +import { CheckIcon, LinkIcon, XIcon } from "@phosphor-icons/react"; import React, { useState } from "react"; import { getDecodedURI } from "../../../../util/uri"; import { MAX_SIZE_PARTICIPANTS } from "../../../constants"; @@ -37,7 +37,7 @@ const InviteUserModal = ({ isOpen, onClose, translate, participantsCount, invite className="absolute right-5 top-4 hover:bg-gray-700 rounded-lg transition-colors text-gray-400 hover:text-white" aria-label="Close dialog" > - +

{translate("meet.invite.invitePeople")} @@ -54,7 +54,7 @@ const InviteUserModal = ({ isOpen, onClose, translate, participantsCount, invite
- + {copied ? (
- + {translate("meet.invite.copied")}
) : ( diff --git a/react/features/base/meet/views/Conference/components/LeaveWithMessageDialog.tsx b/react/features/base/meet/views/Conference/components/LeaveWithMessageDialog.tsx new file mode 100644 index 000000000000..6d03f47a8d78 --- /dev/null +++ b/react/features/base/meet/views/Conference/components/LeaveWithMessageDialog.tsx @@ -0,0 +1,71 @@ +import React, { useEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { makeStyles } from 'tss-react/mui'; + +import Dialog from '../../../../ui/components/web/Dialog'; + +const useStyles = makeStyles()(theme => { + return { + dialog: { + marginBottom: theme.spacing(1) + }, + + title: { + fontSize: '1.25rem', + fontWeight: 'bold', + textAlign: 'left' + }, + + text: { + fontSize: '1.25rem' + } + }; +}); + +/** + * The type of the React {@code Component} props of {@link LeaveReasonDialog}. + */ +interface IProps { + + /** + * Callback invoked when {@code LeaveReasonDialog} is unmounted. + */ + onClose: () => void; + + /** + * The title to display in the dialog. + */ + title?: string; + + message?: string; +} + +/** + * A React {@code Component} for displaying a dialog with a reason that ended the conference. + * + * @param {IProps} props - Component's props. + * @returns {JSX} + */ +const LeaveWithMessageDialog = ({ onClose, title, message }: IProps) => { + const { classes } = useStyles(); + const { t } = useTranslation(); + + useEffect(() => () => { + onClose?.(); + }, []); + + return ( + +
+ {title ?
{t(title)}
: null} + {message ?
{t(message)}
: null} +
+
+ ); +}; + +export default LeaveWithMessageDialog; \ No newline at end of file diff --git a/react/features/base/meet/views/Conference/containers/VideoGalleryWrapper.tsx b/react/features/base/meet/views/Conference/containers/VideoGalleryWrapper.tsx index 65595453d6f3..0e2db7858722 100644 --- a/react/features/base/meet/views/Conference/containers/VideoGalleryWrapper.tsx +++ b/react/features/base/meet/views/Conference/containers/VideoGalleryWrapper.tsx @@ -1,4 +1,4 @@ -import { CaretDown, CaretUp, CheckCircle, MonitorArrowUp } from "@phosphor-icons/react"; +import { CaretDownIcon, CaretUpIcon, CheckCircleIcon, MonitorArrowUpIcon } from "@phosphor-icons/react"; import React, { useRef, useState } from "react"; import { WithTranslation } from "react-i18next"; import { connect, useSelector } from "react-redux"; @@ -93,9 +93,9 @@ const GalleryVideoWrapper = ({ videoMode, t, dispatch }: GalleryVideoWrapperProp
{isLocalSharing ? ( - + ) : ( - + )} {isLocalSharing @@ -139,7 +139,7 @@ const GalleryVideoWrapper = ({ videoMode, t, dispatch }: GalleryVideoWrapperProp : "opacity-30 cursor-not-allowed" }`} > - + )} @@ -171,7 +171,7 @@ const GalleryVideoWrapper = ({ videoMode, t, dispatch }: GalleryVideoWrapperProp : "opacity-30 cursor-not-allowed" }`} > - + )}
diff --git a/react/features/base/meet/views/Home/components/auth/PasswordInput.tsx b/react/features/base/meet/views/Home/components/auth/PasswordInput.tsx index da4e6950a5fb..2f97fcc638e6 100644 --- a/react/features/base/meet/views/Home/components/auth/PasswordInput.tsx +++ b/react/features/base/meet/views/Home/components/auth/PasswordInput.tsx @@ -1,4 +1,4 @@ -import { Eye, EyeSlash } from "@phosphor-icons/react"; +import { EyeIcon, EyeSlashIcon } from "@phosphor-icons/react"; import React, { useState } from "react"; import { FieldError, Path, UseFormRegister, ValidationRule } from "react-hook-form"; import { IFormValues } from "../../types"; @@ -85,7 +85,7 @@ const PasswordInput = ({ tabIndex={0} className="absolute right-4 top-1/2 flex -translate-y-1/2 cursor-pointer items-center justify-center text-gray-100" > - {showPassword ? : } + {showPassword ? : }
{error &&

{error.message}

} diff --git a/react/features/base/meet/views/Home/containers/AuthModal.tsx b/react/features/base/meet/views/Home/containers/AuthModal.tsx index 43bc6d59eebe..09d29734ced7 100644 --- a/react/features/base/meet/views/Home/containers/AuthModal.tsx +++ b/react/features/base/meet/views/Home/containers/AuthModal.tsx @@ -1,5 +1,5 @@ import { Modal } from "@internxt/ui"; -import { X } from "@phosphor-icons/react"; +import { XIcon } from "@phosphor-icons/react"; import React, { useEffect, useState } from "react"; import { LoginCredentials } from "../../../services/types/command.types"; import { WebAuthButton } from "../components/auth/WebAuthButton"; @@ -103,7 +103,7 @@ const ModalHeader: React.FC<{ {isLoginView ? translate("meet.auth.modal.title") : translate("meet.auth.modal.signup.createAccountTitle")}
); diff --git a/react/features/base/meet/views/Home/containers/ScheduleModal.tsx b/react/features/base/meet/views/Home/containers/ScheduleModal.tsx index efeefc43af4c..96430b99a122 100644 --- a/react/features/base/meet/views/Home/containers/ScheduleModal.tsx +++ b/react/features/base/meet/views/Home/containers/ScheduleModal.tsx @@ -1,5 +1,5 @@ import { Button, Input, Modal } from "@internxt/ui"; -import { Copy, X } from "@phosphor-icons/react"; +import { CopyIcon, XIcon } from "@phosphor-icons/react"; import React, { useState } from "react"; interface ScheduleMeetingModalProps { @@ -32,7 +32,7 @@ const ScheduleMeetingModal: React.FC = ({ return (

{translate("meet.modals.schedule.title")}

@@ -57,7 +57,7 @@ const ScheduleMeetingModal: React.FC = ({ onClick={handleCopy} className="absolute right-1.5 top-1 h-8 w-8 rounded-full items-center justify-center flex hover:bg-[#0066FF1A] active:bg-[#0066FF40] transition-colors duration-200" > - + {copied && (
diff --git a/react/features/base/meet/views/PreMeeting/PreMeetingScreen.tsx b/react/features/base/meet/views/PreMeeting/PreMeetingScreen.tsx index 57647dc1f66b..804883a7466a 100644 --- a/react/features/base/meet/views/PreMeeting/PreMeetingScreen.tsx +++ b/react/features/base/meet/views/PreMeeting/PreMeetingScreen.tsx @@ -271,9 +271,10 @@ const PreMeetingScreen = ({ getUsersInMeeting(); } - if (userData?.name) { + const name = storageManager.getDisplayName() || userData?.name; + if (name) { dispatchUpdateSettings({ - displayName: userData.name, + displayName: name, }); } }, []); @@ -297,28 +298,12 @@ const PreMeetingScreen = ({ } }; - const updateNameInStorage = (name: string) => { - try { - const user = storageManager.getUser(); - - if (user) { - const updatedUser = { - ...user, - name: name, - }; - - storageManager.setUser(updatedUser); - } - } catch (error) { - console.error("Error updating user name in localStorage:", error); - } - }; const setName = (displayName: string) => { dispatchUpdateSettings({ displayName, }); - updateNameInStorage(displayName); + storageManager.setDisplayName(displayName); }; const onLogout = () => { @@ -389,7 +374,7 @@ const PreMeetingScreen = ({ disableJoinButton={disableJoinButton || isCreatingMeeting} flipX={flipX} isCreatingConference={!!createConference} - errorMessage={errorMessage} + errorMessage={t(errorMessage)} /> {triggerText} -
- {item.value === selectedDeviceId && } + {item.value === selectedDeviceId && }
{item.label}
diff --git a/react/features/base/meet/views/Settings/SettingsDialog.tsx b/react/features/base/meet/views/Settings/SettingsDialog.tsx index a3e22b4605c3..f26b116322cc 100644 --- a/react/features/base/meet/views/Settings/SettingsDialog.tsx +++ b/react/features/base/meet/views/Settings/SettingsDialog.tsx @@ -1,4 +1,4 @@ -import { Icon, X } from "@phosphor-icons/react"; +import { Icon, XIcon } from "@phosphor-icons/react"; import React, { ComponentType, useCallback, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -177,7 +177,7 @@ const SettingsDialog: React.FC = ({ className="absolute right-5 top-4 hover:bg-gray-700 rounded-lg transition-colors text-gray-400 hover:text-white" aria-label="Close dialog" > - +
{/* Tab content */} diff --git a/react/features/base/meet/views/Settings/SettingsDialogWrapper.tsx b/react/features/base/meet/views/Settings/SettingsDialogWrapper.tsx index ee09c7e5eac6..0021cb18be25 100644 --- a/react/features/base/meet/views/Settings/SettingsDialogWrapper.tsx +++ b/react/features/base/meet/views/Settings/SettingsDialogWrapper.tsx @@ -1,4 +1,4 @@ -import { ArrowSquareOut } from "@phosphor-icons/react"; +import { ArrowSquareOutIcon } from "@phosphor-icons/react"; import React, { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; @@ -38,7 +38,7 @@ const SettingsDialogWrapper: React.FC = ({ generalTabs, defaultTab, disp id: "manage-account", label: t("settings.account.manage"), onClick: () => window.open(EXTERNAL_ACCOUNT_URL, "_blank", "noopener,noreferrer"), - Icon: ArrowSquareOut, + Icon: ArrowSquareOutIcon, }, ]; diff --git a/react/features/conference/actions.web.ts b/react/features/conference/actions.web.ts index 7697f665ac65..ab79ebfb22c6 100644 --- a/react/features/conference/actions.web.ts +++ b/react/features/conference/actions.web.ts @@ -5,7 +5,7 @@ import { getJitsiMeetGlobalNSConnectionTimes } from '../base/util/helpers'; import { getBackendSafeRoomName } from '../base/util/uri'; import { DISMISS_CALENDAR_NOTIFICATION } from './actionTypes'; -import LeaveReasonDialog from './components/web/LeaveReasonDialog.web'; +import LeaveWithMessageDialog from '../base/meet/views/Conference/components/LeaveWithMessageDialog'; import logger from './logger'; /** @@ -15,11 +15,12 @@ import logger from './logger'; * * @returns {Promise} Resolved when the dialog is closed. */ -export function openLeaveReasonDialog(title?: string) { +export function openLeaveReasonDialog(title?: string, message?: string) { return (dispatch: IStore['dispatch']): Promise => new Promise(resolve => { - dispatch(openDialog('LeaveReasonDialog', LeaveReasonDialog, { + dispatch(openDialog('LeaveWithMessageDialog', LeaveWithMessageDialog, { onClose: resolve, - title + title, + message })); }); } diff --git a/react/features/conference/middleware.web.ts b/react/features/conference/middleware.web.ts index abfe1f15787a..44db75122dfc 100644 --- a/react/features/conference/middleware.web.ts +++ b/react/features/conference/middleware.web.ts @@ -2,7 +2,6 @@ import i18next from 'i18next'; import { ENDPOINT_MESSAGE_RECEIVED, KICKED_OUT } from '../base/conference/actionTypes'; import { hangup } from '../base/connection/actions.web'; -import { getParticipantDisplayName } from '../base/participants/functions'; import MiddlewareRegistry from '../base/redux/MiddlewareRegistry'; import { openAllowToggleCameraDialog, setCameraFacingMode } from '../base/tracks/actions.web'; import { CAMERA_FACING_MODE_MESSAGE } from '../base/tracks/constants'; @@ -25,24 +24,20 @@ MiddlewareRegistry.register(store => next => action => { case KICKED_OUT: { const { dispatch, getState } = store; - const { participant } = action; const { room } = getState()["features/base/conference"]; // we need to first finish dispatching or the notification can be cleared out const result = next(action); - const participantDisplayName - = getParticipantDisplayName(store.getState, participant.getId()); - const roomId = room ?? ""; + const roomId = room ?? ""; - dispatch( + dispatch( hangup( true, roomId, - participantDisplayName - ? i18next.t("dialog.kickTitle", { participantDisplayName }) - : i18next.t("dialog.kickSystemTitle"), - true + i18next.t("dialog.kickDuplicateTitle"), + true, + i18next.t("dialog.kickDuplicateMessage"), ) ); diff --git a/react/features/settings/components/web/audio/AudioSettingsContent.tsx b/react/features/settings/components/web/audio/AudioSettingsContent.tsx index 1f650a386586..229d621551b3 100644 --- a/react/features/settings/components/web/audio/AudioSettingsContent.tsx +++ b/react/features/settings/components/web/audio/AudioSettingsContent.tsx @@ -16,7 +16,7 @@ import { isNoiseSuppressionEnabled } from "../../../../noise-suppression/functio import { isPrejoinPageVisible } from "../../../../prejoin/functions"; import { createLocalAudioTracks } from "../../../functions.web"; -import { Microphone, SpeakerSimpleHigh } from "@phosphor-icons/react"; +import { MicrophoneIcon, SpeakerSimpleHighIcon } from "@phosphor-icons/react"; import MicrophoneEntry from "./MicrophoneEntry"; import SpeakerEntry from "./SpeakerEntry"; @@ -305,7 +305,7 @@ const AudioSettingsContent = ({ } + icon={() => } id={speakerHeaderId} text={t("settings.speakers")} /> @@ -321,7 +321,7 @@ const AudioSettingsContent = ({ } + icon={() => } id={microphoneHeaderId} text={t("settings.microphones")} /> diff --git a/react/features/settings/components/web/video/VideoSettingsContent.tsx b/react/features/settings/components/web/video/VideoSettingsContent.tsx index 133610f637fa..1ddbf7345001 100644 --- a/react/features/settings/components/web/video/VideoSettingsContent.tsx +++ b/react/features/settings/components/web/video/VideoSettingsContent.tsx @@ -3,7 +3,7 @@ import { useTranslation } from "react-i18next"; import { connect } from "react-redux"; import { makeStyles } from "tss-react/mui"; -import { VideoCamera } from "@phosphor-icons/react"; +import { VideoCameraIcon } from "@phosphor-icons/react"; import { IReduxState, IStore } from "../../../../app/types"; import { IconImage } from "../../../../base/icons/svg"; import { Video } from "../../../../base/media/components/index.web"; @@ -362,7 +362,7 @@ const VideoSettingsContent = ({ {selectedTrack && _renderPreviewEntry(selectedTrack)}
} + icon={() => } text={t("meet.settings.video.videoInput")} accessibilityLabel={t("meet.settings.video.videoInput")} /> diff --git a/static/offline.html b/static/offline.html index 293949eb85ce..76f4397df10e 100644 --- a/static/offline.html +++ b/static/offline.html @@ -8,7 +8,7 @@ - +