From c0feea48e52fe17514b7e417745ccc73e5190b35 Mon Sep 17 00:00:00 2001
From: SD 
Date: Thu, 21 Nov 2024 17:03:49 +0400
Subject: [PATCH] feat: create rooms
---
 src/actions/hooks/useRoomDetails.ts           |  42 +++
 src/actions/rooms.ts                          | 109 ++++++
 .../account/(simple)/rooms/[...slug]/page.tsx |  57 +++
 .../[locale]/account/(simple)/rooms/page.tsx  |  12 +
 src/components/Account/agora/Agora.tsx        |   2 +-
 src/components/Account/agora/AgoraGroup.tsx   |  54 +++
 .../agora/components/UsersGroupPanel.tsx      |  44 +++
 .../Account/agora/components/index.ts         |   1 +
 src/components/Account/agora/index.tsx        |  16 +
 src/components/Account/index.ts               |   1 +
 src/components/Account/rooms/CreateRoom.tsx   |  46 +++
 src/components/Account/rooms/EditRoomForm.tsx | 220 +++++++++++
 src/components/Account/rooms/RoomDetails.tsx  |  66 ++++
 .../Account/rooms/RoomDetailsContent.tsx      | 355 ++++++++++++++++++
 src/components/Account/rooms/RoomsTabs.tsx    | 173 +++++++++
 src/components/Account/rooms/index.tsx        |   6 +
 .../sessions/SessionDetailsContent.tsx        |   6 +-
 src/components/Experts/AdditionalFilter.tsx   |   2 -
 .../Modals/EditExpertEducationModal.tsx       |  20 +-
 src/components/Modals/ScheduleModal.tsx       |  28 +-
 src/components/Modals/UsersListModal.tsx      | 113 ++++++
 src/components/view/CustomDatePicker.tsx      |  60 +++
 src/i18nKeys/de.ts                            |  19 +-
 src/i18nKeys/en.ts                            |  19 +-
 src/i18nKeys/es.ts                            |  19 +-
 src/i18nKeys/fr.ts                            |  19 +-
 src/i18nKeys/it.ts                            |  19 +-
 src/i18nKeys/ru.ts                            |  19 +-
 src/styles/_default.scss                      |   1 +
 src/styles/_modal.scss                        |   7 +
 src/styles/_pages.scss                        |   4 +
 src/styles/sessions/_agora.scss               |  53 ++-
 src/styles/sessions/_details.scss             |  89 +++++
 src/styles/view/_calendar.scss                |   1 -
 src/styles/view/_datepicker.scss              | 128 +++++++
 src/styles/view/_room.scss                    |  86 +++++
 src/styles/view/_select.scss                  |   3 +-
 src/styles/view/_timepicker.scss              |   2 +-
 src/styles/view/style.scss                    |   2 +
 src/types/author.ts                           |   1 -
 src/types/rooms.ts                            |  44 +++
 src/types/sessions.ts                         |   3 +
 src/utils/account.ts                          |   2 +-
 src/utils/locale.ts                           |  28 ++
 44 files changed, 1946 insertions(+), 55 deletions(-)
 create mode 100644 src/actions/hooks/useRoomDetails.ts
 create mode 100644 src/actions/rooms.ts
 create mode 100644 src/app/[locale]/account/(simple)/rooms/[...slug]/page.tsx
 create mode 100644 src/app/[locale]/account/(simple)/rooms/page.tsx
 create mode 100644 src/components/Account/agora/AgoraGroup.tsx
 create mode 100644 src/components/Account/agora/components/UsersGroupPanel.tsx
 create mode 100644 src/components/Account/rooms/CreateRoom.tsx
 create mode 100644 src/components/Account/rooms/EditRoomForm.tsx
 create mode 100644 src/components/Account/rooms/RoomDetails.tsx
 create mode 100644 src/components/Account/rooms/RoomDetailsContent.tsx
 create mode 100644 src/components/Account/rooms/RoomsTabs.tsx
 create mode 100644 src/components/Account/rooms/index.tsx
 create mode 100644 src/components/Modals/UsersListModal.tsx
 create mode 100644 src/components/view/CustomDatePicker.tsx
 create mode 100644 src/styles/view/_datepicker.scss
 create mode 100644 src/styles/view/_room.scss
 create mode 100644 src/types/rooms.ts
 create mode 100644 src/utils/locale.ts
diff --git a/src/actions/hooks/useRoomDetails.ts b/src/actions/hooks/useRoomDetails.ts
new file mode 100644
index 0000000..029337f
--- /dev/null
+++ b/src/actions/hooks/useRoomDetails.ts
@@ -0,0 +1,42 @@
+'use client'
+
+import { useCallback, useEffect, useState } from 'react';
+import { useLocalStorage } from '../../hooks/useLocalStorage';
+import { AUTH_TOKEN_KEY } from '../../constants/common';
+import { Room } from '../../types/rooms';
+import { getRoomDetails } from '../rooms';
+
+export const useRoomDetails = (locale: string, roomId: number) => {
+    const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
+    const [room, setRoom] = useState();
+    const [errorData, setErrorData] = useState();
+    const [loading, setLoading] = useState(false);
+
+    const fetchData = useCallback(() => {
+        setLoading(true);
+        setErrorData(undefined);
+        setRoom(undefined);
+
+        getRoomDetails(locale, jwt, roomId)
+            .then((room) => {
+                setRoom(room);
+            })
+            .catch((err) => {
+                setErrorData(err);
+            })
+            .finally(() => {
+                setLoading(false);
+            })
+    }, []);
+
+    useEffect(() => {
+        fetchData();
+    }, []);
+
+    return {
+        fetchData,
+        loading,
+        room,
+        errorData
+    };
+};
diff --git a/src/actions/rooms.ts b/src/actions/rooms.ts
new file mode 100644
index 0000000..dd8f03d
--- /dev/null
+++ b/src/actions/rooms.ts
@@ -0,0 +1,109 @@
+import { apiRequest } from './helpers';
+import {GetUsersForRooms, Room, RoomEdit, RoomEditDTO} from '../types/rooms';
+
+export const getUpcomingRooms = (locale: string, token: string): Promise => apiRequest({
+    url: '/home/upcomingsessionsall',
+    method: 'post',
+    data: {
+        sessionType: 'room'
+    },
+    locale,
+    token
+});
+
+export const getRecentRooms = (locale: string, token: string): Promise => apiRequest({
+    url: '/home/historicalmeetings',
+    method: 'post',
+    data: {
+        sessionType: 'room'
+    },
+    locale,
+    token
+});
+
+export const getRoomDetails = (locale: string, token: string, id: number): Promise => apiRequest({
+    url: '/home/room',
+    method: 'post',
+    data: { id },
+    locale,
+    token
+});
+
+export const deleteRoomClient = (locale: string, token: string, data: { sessionId: number, clientUserId: number }): Promise => apiRequest({
+    url: '/home/deleteclientfromroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const deleteRoomSupervisor = (locale: string, token: string, data: { sessionId: number, supervisorUserId: number }): Promise => apiRequest({
+    url: '/home/deletesupervisorfromroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const becomeRoomClient = (locale: string, token: string, data: { sessionId: number, clientUserId: number }): Promise => apiRequest({
+    url: '/home/becomeroomclient',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const becomeRoomSupervisor = (locale: string, token: string, data: { sessionId: number, supervisorUserId: number }): Promise => apiRequest({
+    url: '/home/becomeroomsupervisor',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const getUsersList = (locale: string, token: string, data: { template: string }): Promise => apiRequest({
+    url: '/home/findusersforroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const addClient = (locale: string, token: string, data: { sessionId: number, clientUserId: number }): Promise => apiRequest({
+    url: '/home/addclienttoroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const addSupervisor = (locale: string, token: string, data: { sessionId: number, supervisorUserId: number }): Promise => apiRequest({
+    url: '/home/addsupervisortoroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const createRoom = (locale: string, token: string): Promise => apiRequest({
+    url: '/home/createroom',
+    method: 'post',
+    locale,
+    token
+});
+
+export const updateRoom = (locale: string, token: string, data: RoomEdit): Promise => apiRequest({
+    url: '/home/updateroom',
+    method: 'post',
+    data,
+    locale,
+    token
+});
+
+export const getRoomById = (locale: string, token: string, id: number): Promise => apiRequest({
+    url: '/home/getroomforedit',
+    method: 'post',
+    data: { id },
+    locale,
+    token
+});
diff --git a/src/app/[locale]/account/(simple)/rooms/[...slug]/page.tsx b/src/app/[locale]/account/(simple)/rooms/[...slug]/page.tsx
new file mode 100644
index 0000000..b6f024c
--- /dev/null
+++ b/src/app/[locale]/account/(simple)/rooms/[...slug]/page.tsx
@@ -0,0 +1,57 @@
+import React, { Suspense } from 'react';
+import { unstable_setRequestLocale } from 'next-intl/server';
+import { notFound } from 'next/navigation';
+import { AccountMenu, RoomDetails, RoomsTabs } from '../../../../../../components/Account';
+import { RoomsType } from '../../../../../../types/rooms';
+
+const ROOMS_ROUTES = [RoomsType.UPCOMING, RoomsType.RECENT, RoomsType.NEW];
+
+export async function generateStaticParams({
+   params: { locale },
+}: { params: { locale: string } }) {
+    return [{ locale, slug: [RoomsType.UPCOMING] }];
+}
+
+export default function RoomsDetailItem({ params: { locale, slug } }: { params: { locale: string, slug?: string[] } }) {
+    unstable_setRequestLocale(locale);
+    const roomType: string = slug?.length > 0 && slug[0] || '';
+    const roomId: number | null = slug?.length > 1 && Number(slug[1]) || null;
+
+    if (!slug?.length || slug?.length > 2) {
+        notFound();
+    }
+
+    if (ROOMS_ROUTES.includes(roomType as RoomsType) && Number.isInteger(roomId)) {
+        return (
+            Loading...
}>
+                
+            
+        );
+    }
+
+    if (ROOMS_ROUTES.includes(roomType as RoomsType)  && !Number.isInteger(roomId)) {
+        return (
+            <>
+                
+                
+            >
+        );
+    }
+
+    return notFound();
+};
diff --git a/src/app/[locale]/account/(simple)/rooms/page.tsx b/src/app/[locale]/account/(simple)/rooms/page.tsx
new file mode 100644
index 0000000..b4ed706
--- /dev/null
+++ b/src/app/[locale]/account/(simple)/rooms/page.tsx
@@ -0,0 +1,12 @@
+'use client';
+
+import { redirect } from 'next/navigation';
+import { useLocalStorage } from '../../../../../hooks/useLocalStorage';
+import { AUTH_TOKEN_KEY } from '../../../../../constants/common';
+import { RoomsType } from '../../../../../types/rooms';
+
+export default function RoomsMainPage() {
+    const [token] = useLocalStorage(AUTH_TOKEN_KEY, '');
+
+    return token ? redirect(`rooms/${RoomsType.UPCOMING}`) : null;
+};
diff --git a/src/components/Account/agora/Agora.tsx b/src/components/Account/agora/Agora.tsx
index 6be491d..79c5bbb 100644
--- a/src/components/Account/agora/Agora.tsx
+++ b/src/components/Account/agora/Agora.tsx
@@ -37,7 +37,7 @@ export const Agora = ({ sessionId, secret, stopCalling, remoteUser }: AgoraProps
     };
 
     return (
-        
+        
             
             
                  void;
+};
+
+export const AgoraGroup = ({ roomId, secret, stopCalling }: AgoraProps) => {
+    const [calling, setCalling] = useState(false);
+    const [micOn, setMic] = useState(false);
+    const [cameraOn, setCamera] = useState(false);
+
+    useEffect(() => {
+        setCalling(true);
+    }, []);
+
+    useJoin(
+        {
+            appid: process.env.NEXT_PUBLIC_AGORA_APPID,
+            channel: `${roomId}-${secret}`,
+            token: null,
+        },
+        calling,
+    );
+
+    const stop = () => {
+        stopCalling();
+        setCalling(false);
+    };
+
+    return (
+        <>
+            
+                
+            
+            
+                 setCamera(a => !a)}
+                    setMic={() => setMic(a => !a)}
+                />
+            
+        >
+    );
+};
diff --git a/src/components/Account/agora/components/UsersGroupPanel.tsx b/src/components/Account/agora/components/UsersGroupPanel.tsx
new file mode 100644
index 0000000..1823c3b
--- /dev/null
+++ b/src/components/Account/agora/components/UsersGroupPanel.tsx
@@ -0,0 +1,44 @@
+import {
+    type IRemoteVideoTrack,
+    useIsConnected, useLocalCameraTrack, useLocalMicrophoneTrack, usePublish,
+    useRemoteAudioTracks,
+    useRemoteUsers,
+    useRemoteVideoTracks
+} from 'agora-rtc-react';
+import { LocalUser } from './LocalUser';
+import { RemoteVideoPlayer } from './RemoteVideoPlayer';
+
+type UsersGroupPanelProps = {
+    calling: boolean;
+    micOn: boolean;
+    cameraOn: boolean;
+};
+
+export const UsersGroupPanel = ({ calling, micOn, cameraOn }: UsersGroupPanelProps) => {
+    const isConnected = useIsConnected();
+    const remoteUsers = useRemoteUsers();
+    const { localMicrophoneTrack } = useLocalMicrophoneTrack(micOn);
+    const { localCameraTrack } = useLocalCameraTrack(cameraOn);
+    const { audioTracks } = useRemoteAudioTracks(remoteUsers);
+
+    usePublish([localMicrophoneTrack, localCameraTrack]);
+    audioTracks.map(track => track.play());
+
+    return calling && isConnected && remoteUsers ? (
+        
+            
+                
+            
+            {remoteUsers.length > 0 && remoteUsers.map(({ uid, videoTrack }) => (
+                
+                    
+                
+            ))}
+        
+                    
+                        
+                    
+                    
+                        
+                    
+                    
+                         ({ value: i+1, label: i+1 }))}
+                        />
+                    
+                    
+                        
+                    
+                
+                
+                    
+                
+                
+            
+        
+    );
+};
diff --git a/src/components/Account/rooms/RoomDetails.tsx b/src/components/Account/rooms/RoomDetails.tsx
new file mode 100644
index 0000000..d3d7a78
--- /dev/null
+++ b/src/components/Account/rooms/RoomDetails.tsx
@@ -0,0 +1,66 @@
+'use client'
+
+import React, { useState, useEffect } from 'react';
+import { RoomsType } from '../../../types/rooms';
+import { useSessionTracking } from '../../../actions/hooks/useSessionTracking';
+import { AccountMenu } from '../AccountMenu';
+import { Loader } from '../../view/Loader';
+import { RoomDetailsContent } from './RoomDetailsContent';
+import { useRoomDetails } from '../../../actions/hooks/useRoomDetails';
+import { AgoraClientGroup } from '../agora';
+
+type RoomDetailsProps = {
+    locale: string;
+    roomId: number;
+    activeType: RoomsType;
+};
+
+export const RoomDetails = ({ roomId, locale, activeType }: RoomDetailsProps) => {
+    const { room, errorData, loading, fetchData } = useRoomDetails(locale, roomId);
+    const tracking = useSessionTracking(locale, roomId);
+    const [isCalling, setIsCalling] = useState(false);
+
+    useEffect(() => {
+        if (isCalling) {
+            tracking.start();
+        } else {
+            tracking.stop();
+        }
+    }, [isCalling]);
+
+    const stopCalling = () => {
+        setIsCalling(false);
+        fetchData();
+    }
+
+    return isCalling
+        ? (
+            
+        ) : (
+            <>
+                
+                
+                    
+                        
+                             setIsCalling(true)}
+                                refresh={fetchData}
+                            />
+                        
+                    
+                
+            
+                
+            
+            
{room?.title || ''}
+            
+                {today
+                    ? `${i18nText('today', locale)} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`
+                    : `${startDate?.format('D MMMM')} ${startDate?.format('HH:mm')} - ${endDate?.format('HH:mm')}`}
+            
+            {room?.themesTags && room.themesTags.length > 0 && (
+                
+                    
+                        {room.themesTags.map((skill) => {skill?.name})}
+                    
+                
+            )}
+            {room?.description && 
{room.description}
}
+            {activeType === RoomsType.UPCOMING && (isCreator || isSupervisor || isClient) && (
+                
+                    {(isCreator || isClient || isSupervisor) && (
+                        
+                    )}
+                    {isCreator && isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && (
+                        
+                    )}
+                
+            )}
+            
+                
+                    
{i18nText('room.roomCreator', locale)}
+                
+                
+                    
+                        
+                            
+                        
+                        
+                            
{`${room?.coach?.name} ${room?.coach?.surname || ''}`}
+                        
+                    
+                
+            
+            {room?.isNeedSupervisor && (
+                
+                    
+                        
{i18nText('room.supervisor', locale)}
+                    
+                    {room?.supervisor && (
+                        
+                            
+                                
+                                    
+                                
+                                
+                                    
{`${room?.supervisor?.name} ${room?.supervisor?.surname || ''}`}
+                                
+                                {isCreator && activeType === RoomsType.UPCOMING && isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && (
+                                    
}
+                                        onClick={() => deleteSupervisor(room?.supervisor?.id)}
+                                    />
+                                )}
+                            
+                        
+                    )}
+                    {room?.supervisor && activeType === RoomsType.RECENT && (
+                        <>
+                            {room?.supervisorComment && (
+                                
{room.supervisorComment}
+                            )}
+                        >
+                    )}
+                    {isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && !room?.supervisor && isCreator && activeType === RoomsType.UPCOMING && (
+                        
+                    )}
+                    {isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && !room?.supervisor && !isCreator && activeType === RoomsType.UPCOMING && checkUserApply() && (
+                        
+                    )}
+                    {!room?.supervisor && !isCreator && !checkUserApply() && (
+                        
{i18nText('noData', locale)}
+                    )}
+                
+            )}
+            
+                
+                    
{i18nText('room.participants', locale)}
+                    
{`${room?.clients?.length || 0}/${room?.maxClients}`}
+                
+                {room?.clients && room?.clients?.length > 0 && (
+                    
+                        {room.clients.map(({id, faceImageUrl, name, surname}) => (
+                            
+                                
+                                    
+                                
+                                
+                                    
{`${name} ${surname || ''}`}
+                                
+                                {isCreator && room?.state === SessionState.COACH_APPROVED && activeType === RoomsType.UPCOMING && isTimeBeforeStart && (
+                                    
}
+                                        onClick={() => deleteClient(id)}
+                                    />
+                                )}
+                            
+                        ))}
+                    
+                )}
+                {isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && isCreator && activeType === RoomsType.UPCOMING && (!room?.clients || (room?.clients && room?.clients?.length < room.maxClients)) && (
+                    
+                )}
+                {isTimeBeforeStart && room?.state === SessionState.COACH_APPROVED && !isCreator && activeType === RoomsType.UPCOMING && (!room?.clients || (room?.clients && room?.clients?.length < room.maxClients)) && checkUserApply() && (
+                    
+                )}
+            
+            {room && (
+                
 setShowModal(false)}
+                    submit={onAddUser}
+                    afterCloseModal={() => setForSupervisor(false)}
+                    room={room}
+                />
+            )}
+        
+            
+                
+            
+            
+        
+                {list && list?.length > 0 ? list?.map(({ id, scheduledStartAtUtc, scheduledEndAtUtc, title, coach, clients, supervisor, maxClients }) => {
+                    const startDate = dayjs(scheduledStartAtUtc).locale(locale);
+                    const endDate = dayjs(scheduledEndAtUtc).locale(locale);
+                    const today = dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD');
+
+                    return (
+                        
) => onClickSession(e, id)}>
+                            
+                                
+                                    

+                                
+                                
+                                    
+                                        
{`${coach?.name} ${coach?.surname || ''}`}
+                                        
{title}
+                                        
+                                            {today
+                                                ? `${i18nText('today', locale)} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`
+                                                : `${startDate.format('D MMMM')} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`}
+                                        
+                                        
+                                            {supervisor && (
+                                                <>
+                                                    
{i18nText('room.supervisor', locale)}
+                                                    
{`${supervisor?.name} ${supervisor?.surname || ''}`}
+                                                >
+                                            )}
+                                            
{i18nText('room.members', locale)}
+                                            
{`${clients.length}/${maxClients}`}
+                                        
+                                    
+                                
+                            
+                        
+                    )
+                }) : (
+                    
+                )}
+            
+                {tabs.map(({ key, label }) => (
+                     router.push(`/account/rooms/${key}`)}
+                    >
+                        {label}
+                    
+                ))}
+            
+            {tabs.filter(({ key }) => key === activeTab)[0].children}
+        
+    );
+};
diff --git a/src/components/Account/rooms/index.tsx b/src/components/Account/rooms/index.tsx
new file mode 100644
index 0000000..4441047
--- /dev/null
+++ b/src/components/Account/rooms/index.tsx
@@ -0,0 +1,6 @@
+'use client'
+
+export * from './RoomDetails';
+export * from './RoomsTabs';
+export * from './RoomDetailsContent';
+export * from './CreateRoom';
diff --git a/src/components/Account/sessions/SessionDetailsContent.tsx b/src/components/Account/sessions/SessionDetailsContent.tsx
index f32622e..212b15c 100644
--- a/src/components/Account/sessions/SessionDetailsContent.tsx
+++ b/src/components/Account/sessions/SessionDetailsContent.tsx
@@ -81,7 +81,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio
     const CoachCard = (coach?: PublicUser) => coach ? (
         
             
-                
+                
             
             
                 
@@ -106,7 +106,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio
                 
                     
                         {session?.themesTags?.slice(0, 2).map((skill) => 
{skill?.name})}
-                        {session?.themesTags?.length > 2
+                        {session?.themesTags && session?.themesTags?.length > 2
                             ? (
                                 
                                     
@@ -128,7 +128,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio
     const StudentCard = (student?: PublicUser | null) => student ? (
         
             
-                
+                
             
             
                 {`${student?.name} ${student?.surname || ''}`}
diff --git a/src/components/Experts/AdditionalFilter.tsx b/src/components/Experts/AdditionalFilter.tsx
index 5b692ce..383c12d 100644
--- a/src/components/Experts/AdditionalFilter.tsx
+++ b/src/components/Experts/AdditionalFilter.tsx
@@ -55,8 +55,6 @@ export const ExpertsAdditionalFilter = ({
         };
         const search = getSearchParamsString(newFilter);
 
-        console.log('here1');
-
         router.push(search ? `${basePath}?${search}#filter` : `${basePath}#filter`);
 
         // router.push({
diff --git a/src/components/Modals/EditExpertEducationModal.tsx b/src/components/Modals/EditExpertEducationModal.tsx
index e2720e5..5d52663 100644
--- a/src/components/Modals/EditExpertEducationModal.tsx
+++ b/src/components/Modals/EditExpertEducationModal.tsx
@@ -1,20 +1,20 @@
 'use client';
 
-import React, { FC, useEffect, useState } from 'react';
-import {Modal, Button, message, Form, Collapse, GetProp, UploadProps} from 'antd';
+import React, { FC, useState } from 'react';
+import { Modal, Button, message, Form, Collapse } from 'antd';
 import type { CollapseProps } from 'antd';
 import { CloseOutlined } from '@ant-design/icons';
 import { i18nText } from '../../i18nKeys';
-import { PracticePersonData, PracticeDTO, PracticeData, PracticeCase } from '../../types/practice';
+import { PracticePersonData } from '../../types/practice';
 import { AUTH_TOKEN_KEY } from '../../constants/common';
 import { useLocalStorage } from '../../hooks/useLocalStorage';
-import {setEducation} from '../../actions/profile';
-import {Certificate, Details, EducationData, EducationDTO, Experience} from "../../types/education";
-import {CertificatesContent} from "./educationModalContent/Certificates";
-import {EducationsContent} from "./educationModalContent/Educations";
-import {TrainingsContent} from "./educationModalContent/Trainings";
-import {MbasContent} from "./educationModalContent/Mbas";
-import {ExperiencesContent} from "./educationModalContent/Experiences";
+import { setEducation } from '../../actions/profile';
+import { EducationData, EducationDTO } from '../../types/education';
+import { CertificatesContent } from './educationModalContent/Certificates';
+import { EducationsContent } from './educationModalContent/Educations';
+import { TrainingsContent } from './educationModalContent/Trainings';
+import { MbasContent } from './educationModalContent/Mbas';
+import { ExperiencesContent } from './educationModalContent/Experiences';
 
 type EditExpertEducationModalProps = {
     open: boolean;
diff --git a/src/components/Modals/ScheduleModal.tsx b/src/components/Modals/ScheduleModal.tsx
index 93ede9c..5397223 100644
--- a/src/components/Modals/ScheduleModal.tsx
+++ b/src/components/Modals/ScheduleModal.tsx
@@ -6,12 +6,6 @@ import { Modal, Menu, Calendar, Radio, Button, Input, message, Form } from 'antd
 import type { CalendarProps, MenuProps } from 'antd';
 import { ArrowLeftOutlined } from '@ant-design/icons';
 import { CloseOutlined } from '@ant-design/icons';
-import locale_ru from 'antd/lib/calendar/locale/ru_RU';
-import locale_en from 'antd/lib/calendar/locale/en_GB';
-import locale_de from 'antd/lib/calendar/locale/de_DE';
-import locale_it from 'antd/lib/calendar/locale/it_IT';
-import locale_es from 'antd/lib/calendar/locale/es_ES';
-import locale_fr from 'antd/lib/calendar/locale/fr_FR';
 import dayjs, { Dayjs } from 'dayjs';
 import 'dayjs/locale/ru';
 import 'dayjs/locale/en';
@@ -19,6 +13,7 @@ import 'dayjs/locale/de';
 import 'dayjs/locale/it';
 import 'dayjs/locale/fr';
 import 'dayjs/locale/es';
+import { getLocale } from '../../utils/locale';
 import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common';
 import { ExpertScheduler, SignupSessionData } from '../../types/experts';
 import { Tag } from '../../types/tags';
@@ -42,27 +37,6 @@ type ScheduleModalProps = {
 
 type MenuItem = Required
['items'][number];
 
-const getLocale = (locale: string) => {
-    if (locale) {
-        switch (locale) {
-            case 'ru':
-                return locale_ru;
-            case 'de':
-                return locale_de;
-            case 'fr':
-                return locale_fr;
-            case 'it':
-                return locale_it;
-            case 'es':
-                return locale_es;
-            default:
-                return locale_en;
-        }
-    }
-
-    return locale_en;
-};
-
 const getCalendarMenu = (start: Dayjs): MenuItem[] => Array.from({ length: 3 })
     .map((_: unknown, index: number) => {
         const date = index ? start.add(index, 'M') : start.clone();
diff --git a/src/components/Modals/UsersListModal.tsx b/src/components/Modals/UsersListModal.tsx
new file mode 100644
index 0000000..f81443d
--- /dev/null
+++ b/src/components/Modals/UsersListModal.tsx
@@ -0,0 +1,113 @@
+'use client';
+
+import React, { useCallback, useState } from 'react';
+import { Button, Modal, notification } from 'antd';
+import { CloseOutlined } from '@ant-design/icons';
+import debounce from 'lodash/debounce';
+import Image from 'next/image';
+import { i18nText } from '../../i18nKeys';
+import { getUsersList } from '../../actions/rooms';
+import { PublicUser } from '../../types/sessions';
+import { Room } from '../../types/rooms';
+import { CustomInput } from '../view/CustomInput';
+import { Loader } from '../view/Loader';
+
+type UserListModalProps = {
+    room: Room;
+    isOpen: boolean;
+    locale: string;
+    handleCancel: () => void;
+    jwt: string;
+    submit: (id: number) => void;
+    afterCloseModal?: () => void;
+};
+
+export const UserListModal = ({ room, isOpen, locale, handleCancel, jwt, submit, afterCloseModal }: UserListModalProps) => {
+    const [users, setUsers] = useState();
+    const [loading, seLoading] = useState(false);
+
+    const onSearch = useCallback(debounce((e: any) => {
+        if (e?.target?.value) {
+            seLoading(true);
+            getUsersList(locale, jwt, { template: e.target.value })
+                .then(({ items }) => {
+                    const clients = room?.clients?.map(({ id }) => id);
+                    setUsers(items
+                        ? items.filter(({ id }) => !(clients?.length && clients.includes(id) || id === room?.supervisor?.id || id === room?.coach?.id))
+                        : undefined);
+                })
+                .catch((err: any) => {
+                    notification.error({
+                        message: 'Error',
+                        description: err?.response?.data?.errMessage
+                    });
+                })
+                .finally(() => {
+                    seLoading(false);
+                });
+        } else {
+            setUsers(undefined);
+        }
+
+    }, 300), []);
+
+    const onAfterClose = () => {
+        setUsers(undefined);
+        if (afterCloseModal) afterCloseModal();
+    }
+
+    return (
+        }
+            afterClose={onAfterClose}
+        >
+            
+                
+                {users && (
+                    
+                        
+                            {users.length > 0 ? (
+                                
+                                    {users.map(({ id, name, surname, faceImageUrl }) => (
+                                        
+                                            
+                                                
+                                                    
+                                                
+                                                
+                                                    
{`${name} ${surname || ''}`}
+                                                
+                                            
+                                            
+                                        
+                                    ))}
+                                
{i18nText('noData', locale)}
+                            )}
+                        
+                    
+                )}
+            
+            
+                {label}
+            
+            
}
+                {...other}
+            />
+