diff --git a/backend/api/__init__.py b/backend/api/__init__.py index 65e0290d0..1dd4d1686 100644 --- a/backend/api/__init__.py +++ b/backend/api/__init__.py @@ -99,6 +99,7 @@ def create_app(): masters, translation, events, + meeting, ) # why blueprints http://flask.pocoo.org/docs/1.0/blueprints/ @@ -118,6 +119,7 @@ def create_app(): app.register_blueprint(mentee.mentee, url_prefix="/api/mentee") app.register_blueprint(messages.messages, url_prefix="/api/messages") app.register_blueprint(notifications.notifications, url_prefix="/api/notifications") + app.register_blueprint(meeting.meeting, url_prefix="/api/meeting") app.register_blueprint(masters.masters, url_prefix="/api/masters") app.register_blueprint(translation.translation, url_prefix="/api/translation") diff --git a/backend/api/utils/jaas_jwt_builder.py b/backend/api/utils/jaas_jwt_builder.py new file mode 100644 index 000000000..b70e5ee4f --- /dev/null +++ b/backend/api/utils/jaas_jwt_builder.py @@ -0,0 +1,169 @@ +import time, uuid +from authlib.jose import jwt + +class JaaSJwtBuilder: + """ + The JaaSJwtBuilder class helps with the generation of the JaaS JWT. + """ + + EXP_TIME_DELAY_SEC = 7200 + # Used as a delay for the exp claim value. + + NBF_TIME_DELAY_SEC = 10 + # Used as a delay for the nbf claim value. + + def __init__(self) -> None: + self.header = { 'alg' : 'RS256' } + self.userClaims = {} + self.featureClaims = {} + self.payloadClaims = {} + + def withDefaults(self): + """Returns the JaaSJwtBuilder with default valued claims.""" + return self.withExpTime(int(time.time() + JaaSJwtBuilder.EXP_TIME_DELAY_SEC)) \ + .withNbfTime(int(time.time() - JaaSJwtBuilder.NBF_TIME_DELAY_SEC)) \ + .withLiveStreamingEnabled(True) \ + .withRecordingEnabled(True) \ + .withOutboundCallEnabled(True) \ + .withTranscriptionEnabled(True) \ + .withModerator(True) \ + .withRoomName('*') \ + .withUserId(str(uuid.uuid4())) + + def withApiKey(self, apiKey): + """ + Returns the JaaSJwtBuilder with the kid claim(apiKey) set. + + :param apiKey A string as the API Key https://jaas.8x8.vc/#/apikeys + """ + self.header['kid'] = apiKey + return self + + def withUserAvatar(self, avatarUrl): + """ + Returns the JaaSJwtBuilder with the avatar claim set. + + :param avatarUrl A string representing the url to get the user avatar. + """ + self.userClaims['avatar'] = avatarUrl + return self + + def withModerator(self, isModerator): + """ + Returns the JaaSJwtBuilder with the moderator claim set. + + :param isModerator A boolean if set to True, user is moderator and False otherwise. + """ + self.userClaims['moderator'] = 'true' if isModerator == True else 'false' + return self + + def withUserName(self, userName): + """ + Returns the JaaSJwtBuilder with the name claim set. + + :param userName A string representing the user's name. + """ + self.userClaims['name'] = userName + return self + + def withUserEmail(self, userEmail): + """ + Returns the JaaSJwtBuilder with the email claim set. + + :param userEmail A string representing the user's email address. + """ + self.userClaims['email'] = userEmail + return self + + def withLiveStreamingEnabled(self, isEnabled): + """ + Returns the JaaSJwtBuilder with the livestreaming claim set. + + :param isEnabled A boolean if set to True, live streaming is enabled and False otherwise. + """ + self.featureClaims['livestreaming'] = 'true' if isEnabled == True else 'false' + return self + + def withRecordingEnabled(self, isEnabled): + """ + Returns the JaaSJwtBuilder with the recording claim set. + + :param isEnabled A boolean if set to True, recording is enabled and False otherwise. + """ + self.featureClaims['recording'] = 'true' if isEnabled == True else 'false' + return self + + def withTranscriptionEnabled(self, isEnabled): + """ + Returns the JaaSJwtBuilder with the transcription claim set. + + :param isEnabled A boolean if set to True, transcription is enabled and False otherwise. + """ + self.featureClaims['transcription'] = 'true' if isEnabled == True else 'false' + return self + + def withOutboundCallEnabled(self, isEnabled): + """ + Returns the JaaSJwtBuilder with the outbound-call claim set. + + :param isEnabled A boolean if set to True, outbound calls are enabled and False otherwise. + """ + self.featureClaims['outbound-call'] = 'true' if isEnabled == True else 'false' + return self + + def withExpTime(self, expTime): + """ + Returns the JaaSJwtBuilder with exp claim set. Use the defaults, you won't have to change this value too much. + + :param expTime Unix time in seconds since epochs plus a delay. Expiration time of the JWT. + """ + self.payloadClaims['exp'] = expTime + return self + + def withNbfTime(self, nbfTime): + """ + Returns the JaaSJwtBuilder with nbf claim set. Use the defaults, you won't have to change this value too much. + + :param nbfTime Unix time in seconds since epochs. + """ + self.payloadClaims['nbfTime'] = nbfTime + return self + + def withRoomName(self, roomName): + """ + Returns the JaaSJwtBuilder with room claim set. + + :param roomName A string representing the room to join. + """ + self.payloadClaims['room'] = roomName + return self + + def withAppID(self, AppId): + """ + Returns the JaaSJwtBuilder with the sub claim set. + + :param AppId A string representing the unique AppID (previously tenant). + """ + self.payloadClaims['sub'] = AppId + return self + + def withUserId(self, userId): + """ + Returns the JaaSJwtBuilder with the id claim set. + + :param A string representing the user, should be unique from your side. + """ + self.userClaims['id'] = userId + return self + + def signWith(self, key): + """ + Returns a signed JWT. + + :param key A string representing the private key in PEM format. + """ + context = { 'user': self.userClaims, 'features': self.featureClaims } + self.payloadClaims['context'] = context + self.payloadClaims['iss'] = 'chat' + self.payloadClaims['aud'] = 'jitsi' + return jwt.encode(self.header, self.payloadClaims, key) diff --git a/backend/api/views/meeting.py b/backend/api/views/meeting.py new file mode 100644 index 000000000..c7d96d70e --- /dev/null +++ b/backend/api/views/meeting.py @@ -0,0 +1,39 @@ +from __future__ import print_function + +import os.path, json +from datetime import datetime, timedelta +from flask import Blueprint +from api.core import create_response +import sys, os + +from api.utils.jaas_jwt_builder import JaaSJwtBuilder + + +meeting = Blueprint("meeting", __name__) + +API_KEY = os.environ.get("EIGHT_X_EIGHT_API_KEY") +APP_ID = os.environ.get("REACT_APP_EIGHT_X_EIGHT_APP_ID") + +@meeting.route("/generateToken", methods=["GET"]) +def generateToken(): + try: + scriptDir = os.path.dirname('/') + fp = os.path.join(scriptDir, 'rsa-private.pem') + + with open(fp, 'r') as reader: + PRIVATE_KEY = reader.read() + jaasJwt = JaaSJwtBuilder() + token = jaasJwt.withDefaults() \ + .withApiKey(API_KEY) \ + .withUserName("User Name") \ + .withUserEmail("email_address@email.com") \ + .withModerator(False) \ + .withAppID(APP_ID) \ + .withUserAvatar("https://asda.com/avatar") \ + .signWith(PRIVATE_KEY) + + return create_response(data={"token": token.decode('utf-8'), "appID": APP_ID}) + + except Exception as error: + print(error) + return create_response(status=422, message=f'Failed to generate token for meeting') \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index e268f0c36..a6beb6034 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -5,6 +5,7 @@ "dependencies": { "@ant-design/icons": "^4.8.0", "@emotion/css": "^11.11.2", + "@jitsi/react-sdk": "^1.4.0", "@reduxjs/toolkit": "^1.9.3", "@testing-library/jest-dom": "^5.16.5", "@testing-library/react": "^14.0.0", @@ -24,10 +25,12 @@ "pretty-quick": "^3.1.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-draggable": "^4.4.6", "react-i18next": "^12.3.1", "react-json-to-table": "^0.1.7", "react-player": "^2.11.2", "react-redux": "^8.1.1", + "react-resizable": "^3.0.5", "react-responsive": "^9.0.2", "react-router": "^5.2.0", "react-router-dom": "^5.2.0", diff --git a/frontend/public/locales/ar/translation.json b/frontend/public/locales/ar/translation.json index b74b047ac..530d8ffa4 100644 --- a/frontend/public/locales/ar/translation.json +++ b/frontend/public/locales/ar/translation.json @@ -93,7 +93,7 @@ "mentor": "مُرشِد", "message": "رسالة", "messages": "رسائل", - "group_message":"رسائل المجموعة", + "group_message":"الدردشة الجماعية", "name": "الاسم", "no": "لا", "other": "آخر", @@ -593,13 +593,18 @@ "resend": "إعادة إرسال" }, "meeting": { - "title": "توليد رابط الاجتماع", - "generateButton": "توليد", + "title": "إنشاء/الانضمام إلى الاجتماع", + "generateButton": "إنشاء غرفة", "cancelButton": "إلغاء", - "generatedURL": "الرابط المُولَد", - "errorGenerating": "فشل في توليد رابط الاجتماع.", - "errorCopy": "فشل في نسخ الرابط إلى الحافظة.", - "copyMessage": "تم نسخ الرابط إلى الحافظة!", - "limitWarning": "يرجى ملاحظة أن الاجتماع سيكون له حد أقصى من 100 مشارك، ولا يوجد حد زمني." - } + "generatedURL": "اسم الغرفة", + "errorGenerating": "فشل في إنشاء الاجتماع.", + "errorCopy": "فشل في نسخ اسم الغرفة إلى الحافظة.", + "copyMessage": "تم نسخ اسم الغرفة إلى الحافظة!", + "limitWarning": "يرجى ملاحظة أن الاجتماع سيقتصر على 100 مشارك، ولن يكون هناك حد زمني.", + "placeHolder": "إنشاء أو لصق اسم الغرفة للانضمام", + "roomName": "يرجى تقديم اسم الغرفة للانضمام.", + "getToken": "غير قادر على الانضمام إلى الاجتماع", + "generateToken": "غير قادر على توليد رمز", + "joinMeeting": "الانضمام إلى الاجتماع" + } } \ No newline at end of file diff --git a/frontend/public/locales/en-US/translation.json b/frontend/public/locales/en-US/translation.json index 932c77603..83a3fd591 100644 --- a/frontend/public/locales/en-US/translation.json +++ b/frontend/public/locales/en-US/translation.json @@ -93,7 +93,7 @@ "mentor": "Mentor", "message": "Message", "messages": "Messages", - "group_message":"Group Messages", + "group_message":"Group Chat", "name": "Name", "no": "No", "confirm": "Confirm", @@ -594,13 +594,18 @@ "resend": "Resend" }, "meeting": { - "title": "Generate Meeting Link", - "generateButton": "Generate", + "title": "Create/Join Meeting", + "generateButton": "Create Room", "cancelButton": "Cancel", - "generatedURL": "Generated URL", - "errorGenerating": "Failed to generate meeting link.", - "errorCopy": "Failed to copy URL to clipboard.", - "copyMessage": "URL copied to clipboard!", - "limitWarning": "Please note that the meeting will have a limit of 100 attendees, and no time limit." + "generatedURL": "Room Name", + "errorGenerating": "Failed to generate meeting.", + "errorCopy": "Failed to copy room name to clipboard.", + "copyMessage": "Room name copied to clipboard!", + "limitWarning": "Please note that the meeting will have a limit of 100 attendees, and no time limit.", + "placeHolder": "Generate or Paste Room Name to Join", + "roomName": "Please provide a room name to join.", + "getToken": "Unable to join meeting", + "generateToken": "Unable to generate token", + "joinMeeting": "Join Meeting" } } \ No newline at end of file diff --git a/frontend/public/locales/es-US/translation.json b/frontend/public/locales/es-US/translation.json index c4ff69627..f4d148f63 100644 --- a/frontend/public/locales/es-US/translation.json +++ b/frontend/public/locales/es-US/translation.json @@ -93,7 +93,7 @@ "mentor": "Mentor", "message": "Mensaje", "messages": "Mensajes", - "group_message":"Mensajes grupales", + "group_message":"Chat de Grupo", "name": "Nombre", "no": "No", "other": "Otro", @@ -593,13 +593,18 @@ "resend": "Reenviar correo electrónico" }, "meeting": { - "title": "Generar Enlace de Reunión", - "generateButton": "Generar", + "title": "Crear/Unirse a la Reunión", + "generateButton": "Crear Sala", "cancelButton": "Cancelar", - "generatedURL": "URL Generada", - "errorGenerating": "Error al generar el enlace de reunión.", - "errorCopy": "Error al copiar la URL al portapapeles.", - "copyMessage": "¡URL copiada al portapapeles!", - "limitWarning": "Por favor, tenga en cuenta que la reunión tendrá un límite de 100 asistentes, y sin límite de tiempo." + "generatedURL": "Nombre de la Sala", + "errorGenerating": "Error al generar la reunión.", + "errorCopy": "Error al copiar el nombre de la sala al portapapeles.", + "copyMessage": "¡Nombre de la sala copiado al portapapeles!", + "limitWarning": "Tenga en cuenta que la reunión tendrá un límite de 100 asistentes y no habrá límite de tiempo.", + "placeHolder": "Genere o pegue el nombre de la sala para unirse", + "roomName": "Por favor proporcione un nombre de sala para unirse.", + "getToken": "No se puede unir a la reunión", + "generateToken": "No se puede generar el token", + "joinMeeting": "Unirse a la Reunión" } } \ No newline at end of file diff --git a/frontend/public/locales/fa-AF/translation.json b/frontend/public/locales/fa-AF/translation.json index 47119f2d6..5b25bbfe4 100644 --- a/frontend/public/locales/fa-AF/translation.json +++ b/frontend/public/locales/fa-AF/translation.json @@ -49,9 +49,6 @@ "traing":{ "completed":"تکمیل شد" }, - "traing":{ - "completed":"تکمیل شد" - }, "appointmentStatus": { "accepted": "تایید شده", "denied": "رد شده", @@ -96,7 +93,7 @@ "mentor": "مربی", "message": "پیام", "messages": "پیام ها", - "group_message":"پیام های گروهی", + "group_message":"گپ گروهی", "name": "نام", "no": "نه", "other": "سایر", @@ -596,13 +593,18 @@ "resend": "ارسال مجدد ایمیل" }, "meeting": { - "title": "تولید لینک جلسه", - "generateButton": "تولید", + "title": "ایجاد/پیوستن به جلسه", + "generateButton": "ایجاد اتاق", "cancelButton": "لغو", - "generatedURL": "لینک تولید شده", - "errorGenerating": "امکان تولید لینک جلسه وجود ندارد.", - "errorCopy": "امکان کپی کردن لینک به کلیپ‌بورد وجود ندارد.", - "copyMessage": "لینک به کلیپ‌بورد کپی شد!", - "limitWarning": "لطفا توجه داشته باشید که جلسه محدودیت ۱۰۰ شرکت‌کننده دارد و محدودیت زمانی ندارد." - } + "generatedURL": "نام اتاق", + "errorGenerating": "ایجاد جلسه ناموفق بود.", + "errorCopy": "کپی نام اتاق به کلیپ بورد ناموفق بود.", + "copyMessage": "نام اتاق به کلیپ بورد کپی شد!", + "limitWarning": "لطفا توجه کنید که جلسه حداکثر ۱۰۰ شرکت کننده خواهد داشت و محدودیت زمانی ندارد.", + "placeHolder": "تولید یا چسباندن نام اتاق برای پیوستن", + "roomName": "لطفا نام اتاق را برای پیوستن وارد کنید.", + "getToken": "نمی توان به جلسه پیوست", + "generateToken": "تولید توکن ناموفق بود", + "joinMeeting": "پیوستن به جلسه" + } } \ No newline at end of file diff --git a/frontend/public/locales/pa-AR/translation.json b/frontend/public/locales/pa-AR/translation.json index aca081e9b..972427ce6 100644 --- a/frontend/public/locales/pa-AR/translation.json +++ b/frontend/public/locales/pa-AR/translation.json @@ -94,7 +94,7 @@ "mentor": "ښوونکی ", "message": "پیغام ", "messages": "يیغامونه", - "group_message":"ګروپ پیغامونه", + "group_message":"ډله ییز چیټ", "name": "نوم", "no": "نه", "other": "بل", @@ -594,13 +594,18 @@ "resend": "بیا لیږنه" }, "meeting": { - "title": "د اړوندو لینک جوړول", - "generateButton": "جوړول", - "cancelButton": "لغوه", - "generatedURL": "جوړ شوي URL", - "errorGenerating": "د اړوندو لینک جوړول ناکامه شو.", - "errorCopy": "URL په کلپ بورډ کاپی کول ناکامه شو.", - "copyMessage": "URL په کلپ بورډ کاپی شو!", - "limitWarning": "مهرباني وکړئ د دوی جوړول له دواړو شمیرې د تر ۱۰۰ کسانو حد او د مهال وقت نلري." - } + "title": "د غونډې جوړول/ګډون کول", + "generateButton": "خونه جوړول", + "cancelButton": "لغوه کول", + "generatedURL": "د خونې نوم", + "errorGenerating": "په غونډه کې ناکام شو.", + "errorCopy": "د خونې نوم په کلېپ بورډ کې کاپي کولو کې ناکام شو.", + "copyMessage": "د خونې نوم په کلېپ بورډ کې کاپي شو!", + "limitWarning": "مهرباني وکړئ په ياد ولرئ چې غونډه به ۱۰۰ ګډونوالو ته محدود وي، او وخت به يې محدود نه وي.", + "placeHolder": "د ګډون لپاره د خونې نوم جوړول یا پیسټ کول", + "roomName": "د ګډون لپاره مهرباني وکړئ د خونې نوم ورکړئ.", + "getToken": "په غونډه کې ګډون نشي کولای", + "generateToken": "ټوکن جوړول ناممکن دي", + "joinMeeting": "په غونډه کې ګډون کول" + } } \ No newline at end of file diff --git a/frontend/public/locales/pt-BR/translation.json b/frontend/public/locales/pt-BR/translation.json index 896a603d5..70a583977 100644 --- a/frontend/public/locales/pt-BR/translation.json +++ b/frontend/public/locales/pt-BR/translation.json @@ -93,7 +93,7 @@ "mentor": "Mentor", "message": "Mensagem", "messages": "Mensagens", - "group_message":"Mensagens de grupo", + "group_message":"Chat em Grupo", "name": "Nome", "no": "Não", "other": "Outro(s)", @@ -593,13 +593,18 @@ "resend": "Reenviar" }, "meeting": { - "title": "Gerar Link da Reunião", - "generateButton": "Gerar", + "title": "Criar/Entrar na Reunião", + "generateButton": "Criar Sala", "cancelButton": "Cancelar", - "generatedURL": "URL Gerado", - "errorGenerating": "Falha ao gerar o link da reunião.", - "errorCopy": "Falha ao copiar URL para a área de transferência.", - "copyMessage": "URL copiado para a área de transferência!", - "limitWarning": "Por favor, note que a reunião terá um limite de 100 participantes e sem limite de tempo." - } + "generatedURL": "Nome da Sala", + "errorGenerating": "Falha ao gerar a reunião.", + "errorCopy": "Falha ao copiar o nome da sala para a área de transferência.", + "copyMessage": "Nome da sala copiado para a área de transferência!", + "limitWarning": "Por favor, note que a reunião terá um limite de 100 participantes, sem limite de tempo.", + "placeHolder": "Gerar ou colar o nome da sala para entrar", + "roomName": "Por favor, forneça um nome de sala para entrar.", + "getToken": "Não foi possível entrar na reunião", + "generateToken": "Não foi possível gerar o token", + "joinMeeting": "Entrar na Reunião" + } } \ No newline at end of file diff --git a/frontend/src/app/App.js b/frontend/src/app/App.js index 14abfd4c9..e8016db9b 100644 --- a/frontend/src/app/App.js +++ b/frontend/src/app/App.js @@ -48,6 +48,7 @@ import { useSelector } from "react-redux"; import { ACCOUNT_TYPE } from "utils/consts"; import { fetchAccounts } from "utils/api"; import CreateMeetingLink from "components/CreateMeetingLink"; +import MeetingPanel from 'components/MeetingPanel'; const { Content } = Layout; @@ -568,6 +569,7 @@ function App() { + diff --git a/frontend/src/app/store.js b/frontend/src/app/store.js index 08171b3bd..f4c8e9a58 100644 --- a/frontend/src/app/store.js +++ b/frontend/src/app/store.js @@ -3,6 +3,7 @@ import userReducer from "features/userSlice"; import notificationsReducer from "features/notificationsSlice"; import messagesReducer from "features/messagesSlice"; import optionsReducer from "features/optionsSlice"; +import meetingPanelReducer from 'features/meetingPanelSlice'; export default configureStore({ reducer: { @@ -10,5 +11,6 @@ export default configureStore({ notifications: notificationsReducer, messages: messagesReducer, options: optionsReducer, + meetingPanel: meetingPanelReducer, }, }); diff --git a/frontend/src/components/CreateMeetingLink.js b/frontend/src/components/CreateMeetingLink.js index 4c123f58f..1cc6a12a8 100644 --- a/frontend/src/components/CreateMeetingLink.js +++ b/frontend/src/components/CreateMeetingLink.js @@ -1,18 +1,31 @@ -import React, { useState } from "react"; +import React, { useState, useEffect , useRef} from "react"; import { Modal, Button, Input, Typography, message } from "antd"; import { CopyOutlined } from "@ant-design/icons"; import { useTranslation } from "react-i18next"; +import { generateToken } from "utils/api"; +import { useDispatch } from 'react-redux'; +import { setPanel, removePanel } from 'features/meetingPanelSlice'; +import { JaaSMeeting } from '@jitsi/react-sdk'; +import { useHistory } from "react-router-dom"; const { Title } = Typography; -function URLGeneration() { +function Meeting() { + const joinButtonRef = useRef(null); const [urlModalVisible, setUrlModalVisible] = useState(true); - const [generatedUrl, setGeneratedUrl] = useState(""); + const [RoomName, setRoomName] = useState(""); + const [Token, setToken] = useState(""); + const [AppID, setAppID] = useState(""); + const [reloadFlag, setReloadFlag] = useState(false); + const history = useHistory(); + const dispatch = useDispatch(); const { t } = useTranslation(); + const ReactAppID = process.env.REACT_APP_EIGHT_X_EIGHT_APP_ID; + const copyToClipboard = () => { try { - navigator.clipboard.writeText(generatedUrl); + navigator.clipboard.writeText(RoomName);; message.success(t("meeting.copyMessage")); } catch (error) { console.error(t("meeting.errorCopy"), error); @@ -20,39 +33,142 @@ function URLGeneration() { } }; - const getURL = () => { + const getRoomName = () => { try { const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; - let result = 'https://jitsi.ff3l.net/'; + let generatedRoomName = ''; for (let i = 0; i < 10; i++) { - result += characters.charAt(Math.floor(Math.random() * characters.length)); + generatedRoomName += characters.charAt(Math.floor(Math.random() * characters.length)); } - setGeneratedUrl(result); + setRoomName(generatedRoomName); } catch (error) { console.error(t("meeting.errorGenerating")); message.error(t("meeting.errorGenerating")); } }; + const createSidePanel = () => { + return ( +
+ { + iframeRef.style.position = 'fixed'; + iframeRef.style.bottom = 0; + iframeRef.style.right = 0; + iframeRef.style.width = '30%'; + iframeRef.style.height = '93vh'; + }} + appId={AppID} + roomName={ReactAppID + '/' + RoomName} + jwt={Token} + configOverwrite={{ + disableThirdPartyRequests: true, + disableLocalVideoFlip: true, + backgroundAlpha: 0.5 + }} + interfaceConfigOverwrite={{ + VIDEO_LAYOUT_FIT: 'nocrop', + MOBILE_APP_PROMO: false, + TILE_VIEW_MAX_COLUMNS: 4 + }} + /> +
+ ); + }; + + const joinMeeting = () => { + try { + if (!RoomName) { + console.error(t("meeting.roomName")); + message.error(t("meeting.roomName")); + return; + } + getToken(); + dispatch(removePanel()); + document.body.style.marginRight = "30%"; + document.body.style.transition = "margin-right 0.3s"; + dispatch(setPanel(createSidePanel())); + } catch (error) { + console.error("Error: ", error); + message.error(t("meeting.getToken")); + } + }; + + const getToken = () => { + try { + if (reloadFlag) { + localStorage.setItem("roomName", RoomName); + window.location.reload(); + } + setReloadFlag(true); + generateToken().then(resp => { + setToken(resp.token); + setAppID(resp.appID); + }).catch(error => { + console.error('Error:', error); + message.error(t("meeting.generateToken")); + }); + } catch (error) { + console.error('Error:', error); + message.error(t("meeting.generateToken")); + } + }; + + const redirectToMessages = () => { + + history.push("/appointments"); + + }; + + useEffect(() => { + const roomNameFromLocalStorage = localStorage.getItem("roomName"); + if (roomNameFromLocalStorage) { + setRoomName(roomNameFromLocalStorage); + localStorage.removeItem("roomName"); + setTimeout(() => { + if (joinButtonRef.current) { + joinButtonRef.current.click(); + } + }, 1000); + } + }, []); + return ( <> setUrlModalVisible(false)} + onCancel={() => { + + setUrlModalVisible(false); + + redirectToMessages(); + + }} footer={[ - , - , +
+ , +
+ , + , +
+
]} >
{t("meeting.generatedURL")}
- + setRoomName(e.target.value)} + placeholder={t("meeting.placeHolder")} + />