Compare commits
	
		
			5 Commits
		
	
	
		
			dbd5eaa014
			...
			eff29677dc
		
	
	| Author | SHA1 | Date | 
|---|---|---|
|  | eff29677dc | |
|  | 2da77f7347 | |
|  | 87b14e8716 | |
|  | 52fba3a879 | |
|  | 61de5c81e7 | 
							
								
								
									
										1
									
								
								.env
								
								
								
								
							
							
						
						
									
										1
									
								
								.env
								
								
								
								
							|  | @ -7,3 +7,4 @@ STRIPE_PAYMENT_DESCRIPTION='BBuddy services' | ||||||
| NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf | NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf | ||||||
| NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc | NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc | ||||||
| NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE | NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE | ||||||
|  | 
 | ||||||
|  |  | ||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							|  | @ -13,6 +13,7 @@ | ||||||
|     "@ant-design/icons": "^5.2.6", |     "@ant-design/icons": "^5.2.6", | ||||||
|     "@ant-design/nextjs-registry": "^1.0.0", |     "@ant-design/nextjs-registry": "^1.0.0", | ||||||
|     "@contentful/rich-text-react-renderer": "^15.22.9", |     "@contentful/rich-text-react-renderer": "^15.22.9", | ||||||
|  |     "@microsoft/signalr": "^8.0.7", | ||||||
|     "@stripe/react-stripe-js": "^2.7.3", |     "@stripe/react-stripe-js": "^2.7.3", | ||||||
|     "@stripe/stripe-js": "^4.1.0", |     "@stripe/stripe-js": "^4.1.0", | ||||||
|     "agora-rtc-react": "2.1.0", |     "agora-rtc-react": "2.1.0", | ||||||
|  | @ -28,6 +29,7 @@ | ||||||
|     "next-intl": "^3.3.1", |     "next-intl": "^3.3.1", | ||||||
|     "react": "^18", |     "react": "^18", | ||||||
|     "react-dom": "^18", |     "react-dom": "^18", | ||||||
|  |     "react-signalr": "^0.2.24", | ||||||
|     "react-slick": "^0.29.0", |     "react-slick": "^0.29.0", | ||||||
|     "react-stripe-js": "^1.1.5", |     "react-stripe-js": "^1.1.5", | ||||||
|     "slick-carousel": "^1.8.1", |     "slick-carousel": "^1.8.1", | ||||||
|  |  | ||||||
|  | @ -0,0 +1,16 @@ | ||||||
|  | import {apiRequest} from "../helpers"; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | export const getChatList = (locale: string, token: string): Promise<any> => apiRequest({ | ||||||
|  |     url: '/chat/chatList', | ||||||
|  |     method: 'get', | ||||||
|  |     locale, | ||||||
|  |     token | ||||||
|  | }); | ||||||
|  | 
 | ||||||
|  | export const getChatMessages = (locale: string, token: string, group: number): Promise<any> => apiRequest({ | ||||||
|  |     url: '/chat/chat_messages/'+group, | ||||||
|  |     method: 'get', | ||||||
|  |     locale, | ||||||
|  |     token | ||||||
|  | }); | ||||||
|  | @ -23,22 +23,22 @@ export const apiRequest = async <T = any, K = any>( | ||||||
| 
 | 
 | ||||||
|         return response.data; |         return response.data; | ||||||
|     } catch (err) { |     } catch (err) { | ||||||
|         // const {
 |         const { | ||||||
|         //     response: {
 |             response: { | ||||||
|         //         status: responseCode = null,
 |                 status: responseCode = null, | ||||||
|         //         statusText = '',
 |                 statusText = '', | ||||||
|         //         data: { message = '', status: errorKey = '' } = {},
 |                 data, | ||||||
|         //     } = {},
 |             } = {}, | ||||||
|         //     code: statusCode = '',
 |             code: statusCode = '', | ||||||
|         // } = err as AxiosError;
 |         } = err as AxiosError; | ||||||
|         //
 | 
 | ||||||
|         // throw new Error(
 |         throw new Error( | ||||||
|         //     JSON.stringify({
 |             JSON.stringify({ | ||||||
|         //         statusCode,
 |                 statusCode, | ||||||
|         //         statusMessage: message || statusText,
 |                 statusMessage: statusText, | ||||||
|         //         responseCode,
 |                 responseCode, | ||||||
|         //         errorKey,
 |                 details: data | ||||||
|         //     }),
 |             }), | ||||||
|         // );
 |         ); | ||||||
|     } |     } | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,8 +1,9 @@ | ||||||
|  | 'use client' | ||||||
| import React from 'react'; | import React from 'react'; | ||||||
| import { unstable_setRequestLocale } from 'next-intl/server'; |  | ||||||
| import { Link } from '../../../../../../navigation'; | import { Link } from '../../../../../../navigation'; | ||||||
| import { i18nText } from '../../../../../../i18nKeys'; | import { i18nText } from '../../../../../../i18nKeys'; | ||||||
| 
 | import {ChatMessages} from "../../../../../../components/Chat/ChatMessages"; | ||||||
|  | /* | ||||||
| export function generateStaticParams({ | export function generateStaticParams({ | ||||||
|     params: { locale }, |     params: { locale }, | ||||||
| }: { params: { locale: string } }) { | }: { params: { locale: string } }) { | ||||||
|  | @ -15,56 +16,22 @@ export function generateStaticParams({ | ||||||
| 
 | 
 | ||||||
|     return result; |     return result; | ||||||
| } | } | ||||||
|  | * | ||||||
|  |  */ | ||||||
| 
 | 
 | ||||||
| export default function Message({ params }: { params: { locale: string, textId: string } }) { | export default function Message({ params: { locale, textId } }: { params: { locale: string, textId: string } }) { | ||||||
|     unstable_setRequestLocale(params.locale); |  | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <ol className="breadcrumb"> |             <ol className="breadcrumb"> | ||||||
|                 <li className="breadcrumb-item"> |                 <li className="breadcrumb-item"> | ||||||
|                     <Link href={'/account/messages' as any}> |                     <Link href={'/account/messages' as any}> | ||||||
|                         {i18nText('accountMenu.messages', params.locale)} |                         {i18nText('accountMenu.messages', locale)} | ||||||
|                     </Link> |                     </Link> | ||||||
|                 </li> |                 </li> | ||||||
|                 <li className="breadcrumb-item active" aria-current="page">{`Person ${params.textId}`}</li> |  | ||||||
|             </ol> |             </ol> | ||||||
| 
 | 
 | ||||||
|             <div className="b-message"> |             <ChatMessages locale={locale} groupId={parseInt(textId)} /> | ||||||
|                 <div className="b-message__inner"> |  | ||||||
|                     <div className="b-message__list b-message__list--me"> |  | ||||||
|                         <div className="b-message__item "> |  | ||||||
|                             <div className="b-message__avatar"> |  | ||||||
|                                 <img src="/images/person.png" className="" alt="" /> |  | ||||||
|                             </div> |  | ||||||
|                             <div className="b-message__text"> |  | ||||||
|                                 🤩 It all for you! |  | ||||||
| 
 |  | ||||||
|                                 <span className="date">07.09.2022</span> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                     <div className="b-message__list"> |  | ||||||
|                         <div className="b-message__item"> |  | ||||||
|                             <div className="b-message__avatar"> |  | ||||||
|                                 <img src="/images/person.png" className="" alt="" /> |  | ||||||
|                             </div> |  | ||||||
|                             <div className="b-message__text"> |  | ||||||
|                                 🤩 It all for you! |  | ||||||
|                                 <span className="date">07.09.2022</span> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                 </div> |  | ||||||
|                 <form className="b-message__form" action=""> |  | ||||||
|                     <textarea placeholder="Type your message here" name="" id="" /> |  | ||||||
|                     <label className="b-message__upload-file"> |  | ||||||
|                         <input type="file" required /> |  | ||||||
|                     </label> |  | ||||||
|                     <div className="b-message__microphone" /> |  | ||||||
|                     <button className="b-message__btn" type="submit" /> |  | ||||||
|                 </form> |  | ||||||
|             </div> |  | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1,12 +1,10 @@ | ||||||
|  | 'use client' | ||||||
| import React, { Suspense } from 'react'; | import React, { Suspense } from 'react'; | ||||||
| import { unstable_setRequestLocale } from 'next-intl/server'; |  | ||||||
| import { Link } from '../../../../../navigation'; |  | ||||||
| import { CustomInput } from '../../../../../components/view/CustomInput'; | import { CustomInput } from '../../../../../components/view/CustomInput'; | ||||||
| import { i18nText } from '../../../../../i18nKeys'; | import { i18nText } from '../../../../../i18nKeys'; | ||||||
|  | import {ChatList} from "../../../../../components/Chat/ChatList"; | ||||||
| 
 | 
 | ||||||
| export default function Messages({ params: { locale } }: { params: { locale: string } }) { | export default function  Messages({ params: { locale } }: { params: { locale: string } }) { | ||||||
|     unstable_setRequestLocale(locale); |  | ||||||
| 
 |  | ||||||
|     return ( |     return ( | ||||||
|         <> |         <> | ||||||
|             <ol className="breadcrumb"> |             <ol className="breadcrumb"> | ||||||
|  | @ -15,74 +13,7 @@ export default function Messages({ params: { locale } }: { params: { locale: str | ||||||
|             <Suspense> |             <Suspense> | ||||||
|                 <CustomInput placeholder={i18nText('name', locale)} /> |                 <CustomInput placeholder={i18nText('name', locale)} /> | ||||||
|             </Suspense> |             </Suspense> | ||||||
|             <div className="messages-session"> |             <ChatList  locale={locale}/> | ||||||
|                 <Link |  | ||||||
|                     className="card-profile" |  | ||||||
|                     href={'messages/1' as any} |  | ||||||
|                 > |  | ||||||
|                     <div className="card-profile__header"> |  | ||||||
|                         <div className="card-profile__header__portrait"> |  | ||||||
|                             <img src="/images/person.png" className="" alt="" /> |  | ||||||
|                         </div> |  | ||||||
|                         <div className="card-profile__header__inner"> |  | ||||||
|                             <div style={{ width: '100%' }}> |  | ||||||
|                                 <div className="card-profile__header__name"> |  | ||||||
|                                     David |  | ||||||
|                                     <span className="count">14</span> |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className="card-profile__header__title"> |  | ||||||
|                                     Lorem ipsum dolor sit at, consecte... |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className="card-profile__header__date "> |  | ||||||
|                                     25 may |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                 </Link> |  | ||||||
|                 <Link |  | ||||||
|                     className="card-profile" |  | ||||||
|                     href={'messages/2' as any} |  | ||||||
|                 > |  | ||||||
|                     <div className="card-profile__header"> |  | ||||||
|                         <div className="card-profile__header__portrait"> |  | ||||||
|                             <img src="/images/person.png" className="" alt="" /> |  | ||||||
|                         </div> |  | ||||||
|                         <div className="card-profile__header__inner"> |  | ||||||
|                             <div style={{ width: '100%' }}> |  | ||||||
|                                 <div className="card-profile__header__name">David</div> |  | ||||||
|                                 <div className="card-profile__header__title"> |  | ||||||
|                                     Lorem ipsum dolor sit at, consecte... |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className="card-profile__header__date "> |  | ||||||
|                                     25 may |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                 </Link> |  | ||||||
|                 <Link |  | ||||||
|                     className="card-profile" |  | ||||||
|                     href={'messages/3' as any} |  | ||||||
|                 > |  | ||||||
|                     <div className="card-profile__header"> |  | ||||||
|                         <div className="card-profile__header__portrait"> |  | ||||||
|                             <img src="/images/person.png" className="" alt="" /> |  | ||||||
|                         </div> |  | ||||||
|                         <div className="card-profile__header__inner"> |  | ||||||
|                             <div style={{ width: '100%' }}> |  | ||||||
|                                 <div className="card-profile__header__name">David</div> |  | ||||||
|                                 <div className="card-profile__header__title"> |  | ||||||
|                                     Lorem ipsum dolor sit at, consecte... |  | ||||||
|                                 </div> |  | ||||||
|                                 <div className="card-profile__header__date "> |  | ||||||
|                                     25 may |  | ||||||
|                                 </div> |  | ||||||
|                             </div> |  | ||||||
|                         </div> |  | ||||||
|                     </div> |  | ||||||
|                 </Link> |  | ||||||
|             </div> |  | ||||||
|         </> |         </> | ||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -4,21 +4,44 @@ import { Button } from 'antd'; | ||||||
| import { useSelectedLayoutSegment, usePathname } from 'next/navigation'; | import { useSelectedLayoutSegment, usePathname } from 'next/navigation'; | ||||||
| import { Link } from '../../navigation'; | import { Link } from '../../navigation'; | ||||||
| import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common'; | import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common'; | ||||||
| import { deleteStorageKey } from '../../hooks/useLocalStorage'; | import {deleteStorageKey, useLocalStorage} from '../../hooks/useLocalStorage'; | ||||||
| import { i18nText } from '../../i18nKeys'; | import { i18nText } from '../../i18nKeys'; | ||||||
| import { getMenuConfig } from '../../utils/account'; | import { getMenuConfig } from '../../utils/account'; | ||||||
|  | import {useEffect, useState} from "react"; | ||||||
|  | import {getChatList} from "../../actions/chat/groups"; | ||||||
| 
 | 
 | ||||||
| export const AccountMenu = ({ locale }: { locale: string }) => { | export const AccountMenu = ({ locale }: { locale: string }) => { | ||||||
|     const selectedLayoutSegment = useSelectedLayoutSegment(); |     const selectedLayoutSegment = useSelectedLayoutSegment(); | ||||||
|     const pathname = selectedLayoutSegment || ''; |     const pathname = selectedLayoutSegment || ''; | ||||||
|     const paths = usePathname(); |     const paths = usePathname(); | ||||||
|     const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale); |     const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale); | ||||||
|  |     const [counts, setCounts] = useState<any>({}); | ||||||
| 
 | 
 | ||||||
|     const onLogout = () => { |     const onLogout = () => { | ||||||
|         deleteStorageKey(AUTH_TOKEN_KEY); |         deleteStorageKey(AUTH_TOKEN_KEY); | ||||||
|         deleteStorageKey(AUTH_USER); |         deleteStorageKey(AUTH_USER); | ||||||
|         window?.location?.replace(`/${paths.split('/')[1]}/`); |         window?.location?.replace(`/${paths.split('/')[1]}/`); | ||||||
|     }; |     }; | ||||||
|  |     const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); | ||||||
|  |     let init = false | ||||||
|  |     useEffect(() => { | ||||||
|  |         if (jwt && locale && !init) { | ||||||
|  |             init = true; | ||||||
|  |             getChatList(locale, jwt).then((payload: any)=> { | ||||||
|  |                 if (payload?.directs) { | ||||||
|  |                     let summ = 0; | ||||||
|  |                     payload?.directs.forEach((el: any) => { | ||||||
|  |                         summ = summ + el.newMessagesCount | ||||||
|  |                     }) | ||||||
|  |                     setCounts({'messages': summ}) | ||||||
|  |                 } | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     }, [jwt, locale]) | ||||||
|  | 
 | ||||||
|  |     const getterCount =  (path: string, count: number)=> { | ||||||
|  |         return counts[path]? counts[path] : count | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     return ( |     return ( | ||||||
|         <ul className="list-sidebar"> |         <ul className="list-sidebar"> | ||||||
|  | @ -26,8 +49,8 @@ export const AccountMenu = ({ locale }: { locale: string }) => { | ||||||
|                 <li key={path} className="list-sidebar__item"> |                 <li key={path} className="list-sidebar__item"> | ||||||
|                     <Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}> |                     <Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}> | ||||||
|                         {title} |                         {title} | ||||||
|                         {count ? ( |                         {getterCount(path, count) ? ( | ||||||
|                             <span className="count">{count}</span> |                             <span className="count">{getterCount(path, count)}</span> | ||||||
|                         ) : null} |                         ) : null} | ||||||
|                     </Link> |                     </Link> | ||||||
|                 </li> |                 </li> | ||||||
|  |  | ||||||
|  | @ -0,0 +1,76 @@ | ||||||
|  | 'use client' | ||||||
|  | import React, {useEffect, useState} from 'react'; | ||||||
|  | import {AUTH_TOKEN_KEY} from '../../constants/common'; | ||||||
|  | import {getChatList, getChatMessages} from "../../actions/chat/groups"; | ||||||
|  | import {useLocalStorage} from "../../hooks/useLocalStorage"; | ||||||
|  | import {Link} from "../../navigation"; | ||||||
|  | import dayjs from "dayjs"; | ||||||
|  | import relativeTime from "dayjs/plugin/relativeTime"; | ||||||
|  | import {message} from "antd"; | ||||||
|  | import {Loader} from "../view/Loader"; | ||||||
|  | 
 | ||||||
|  | dayjs.extend(relativeTime); | ||||||
|  | 
 | ||||||
|  | type CompProps = { | ||||||
|  |     locale: string; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const ChatList = ({  locale }: CompProps) => { | ||||||
|  |     const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); | ||||||
|  |     const [chats, setСhats] = useState<any | undefined>(); | ||||||
|  |     const [loading, setLoading] = useState<boolean>(false); | ||||||
|  |     useEffect(() => { | ||||||
|  |         if (jwt) { | ||||||
|  |             setLoading(true); | ||||||
|  |             Promise.all([ | ||||||
|  |                 getChatList(locale, jwt), | ||||||
|  |             ]) | ||||||
|  |                 .then(([_groups]) => { | ||||||
|  |                    setСhats(_groups) | ||||||
|  |                 }) | ||||||
|  |                 .catch((e) => { | ||||||
|  |                     console.log(e) | ||||||
|  |                     message.error('Не удалось загрузить данные'); | ||||||
|  |                 }) | ||||||
|  |                 .finally(() => { | ||||||
|  |                     setLoading(false); | ||||||
|  |                 }) | ||||||
|  |         } | ||||||
|  |     },[jwt]) | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  |         <div className="messages-session"> | ||||||
|  |             <Loader isLoading={loading}> | ||||||
|  |             {chats?.directs.map((item: any, i: number) => ( | ||||||
|  |                 <Link | ||||||
|  |                     key={'chat'+i} | ||||||
|  |                     className="card-profile" | ||||||
|  |                     href={'messages/'+item.group.id as any} | ||||||
|  |                 > | ||||||
|  |                     <div className="card-profile__header"> | ||||||
|  |                         <div className="card-profile__header__portrait"> | ||||||
|  |                             <img src={item.faceImageUrl} className="" alt="" /> | ||||||
|  |                         </div> | ||||||
|  |                         <div className="card-profile__header__inner"> | ||||||
|  |                             <div style={{ width: '100%' }}> | ||||||
|  |                                 <div className="card-profile__header__name"> | ||||||
|  |                                     {item.firstName} | ||||||
|  |                                     {item.newMessagesCount && ( | ||||||
|  |                                         <span className="count">{item.newMessagesCount}</span> | ||||||
|  |                                     )} | ||||||
|  |                                 </div> | ||||||
|  |                                 <div className="card-profile__header__title"> | ||||||
|  |                                     {item?.lastMessage?.text} | ||||||
|  |                                 </div> | ||||||
|  |                                 <div className="card-profile__header__date "> | ||||||
|  |                                     {dayjs(item?.lastMessage?.sentAt).fromNow()} | ||||||
|  |                                 </div> | ||||||
|  |                             </div> | ||||||
|  |                         </div> | ||||||
|  |                     </div> | ||||||
|  |                 </Link> | ||||||
|  |             ))} | ||||||
|  |             </Loader> | ||||||
|  |         </div> | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -0,0 +1,200 @@ | ||||||
|  | 'use client' | ||||||
|  | import React, {useEffect, useState} from 'react'; | ||||||
|  | import {AUTH_TOKEN_KEY} from '../../constants/common'; | ||||||
|  | import {getChatList, getChatMessages} from "../../actions/chat/groups"; | ||||||
|  | import {useLocalStorage} from "../../hooks/useLocalStorage"; | ||||||
|  | import dayjs from "dayjs"; | ||||||
|  | import relativeTime from "dayjs/plugin/relativeTime"; | ||||||
|  | 
 | ||||||
|  | import {message} from "antd"; | ||||||
|  | import {Loader} from "../view/Loader"; | ||||||
|  | import SignalrConnection from "../../lib/signalr-connection"; | ||||||
|  | import {CheckOutlined} from "@ant-design/icons"; | ||||||
|  | 
 | ||||||
|  | dayjs.extend(relativeTime); | ||||||
|  | 
 | ||||||
|  | type CompProps = { | ||||||
|  |     locale: string; | ||||||
|  |     groupId: number | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const ChatMessages = ({  locale, groupId }: CompProps) => { | ||||||
|  |     const { newMessage, joinChat, readMessages, addListener } = SignalrConnection(); | ||||||
|  |     const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); | ||||||
|  |     //const messages = await getChatMessages(locale, jwt, groupId)
 | ||||||
|  |     const [loading, setLoading] = useState<boolean>(false); | ||||||
|  |     const [text, setText] = useState(''); | ||||||
|  |     const [me, setMe] = useState<any | undefined>(); | ||||||
|  |     const [notMe, setNotMe] = useState<any | undefined>(); | ||||||
|  |     const [messages, setMessages] = useState<any[]>([]); | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         if (jwt) { | ||||||
|  |             setLoading(true); | ||||||
|  |             Promise.all([ | ||||||
|  |                 getChatList(locale, jwt), | ||||||
|  |                 getChatMessages(locale, jwt, groupId), | ||||||
|  |             ]) | ||||||
|  |                 .then(([_groups, _messages]) => { | ||||||
|  |                     //
 | ||||||
|  |                     const _group = _groups.directs.find( (el: any) => el.group.id === groupId) | ||||||
|  |                     if (_group.group.members[0].userId === parseInt(_group.userId)){ | ||||||
|  |                         setMe(_group.group.members[0].user) | ||||||
|  |                         setNotMe(_group.group.members[1].user) | ||||||
|  |                     } else { | ||||||
|  |                         setMe(_group.group.members[1].user) | ||||||
|  |                         setNotMe(_group.group.members[0].user) | ||||||
|  |                     } | ||||||
|  |                     setMessages(_messages.messages); | ||||||
|  | 
 | ||||||
|  |                 }) | ||||||
|  |                 .catch((e) => { | ||||||
|  |                     console.log(e) | ||||||
|  |                     message.error('Не удалось загрузить данные'); | ||||||
|  |                 }) | ||||||
|  |                 .finally(() => { | ||||||
|  |                     setLoading(false); | ||||||
|  |                 }) | ||||||
|  |         } | ||||||
|  |     },[jwt]) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     const onConnected = (flag: boolean) =>{ | ||||||
|  |         if (flag) { | ||||||
|  |             joinChat(groupId) | ||||||
|  |             readUreaded() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const readUreaded = () => { | ||||||
|  |         const msgs = [] as number[]; | ||||||
|  |         messages.forEach((message: any) => { | ||||||
|  |             if (!message.seen && message.sentByMe === false){ | ||||||
|  |                 msgs.push(message.id) | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         readMessages(msgs) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const onReceiveMessage = (payload: any) => { | ||||||
|  |         const _messages = [... messages]; | ||||||
|  |         let flag = false; | ||||||
|  |         const msg = { | ||||||
|  |             data : payload.data, | ||||||
|  |             dataType: null, | ||||||
|  |             id: payload.id, | ||||||
|  |             receiverId:null, | ||||||
|  |             seen: false, | ||||||
|  |             senderId: payload.creatorId, | ||||||
|  |             sentAt: payload.createdUtc+'.000Z', | ||||||
|  |             sentByMe: payload.creatorId === me.id, | ||||||
|  |             text: payload.content, | ||||||
|  |             type:"text" | ||||||
|  |         } | ||||||
|  |         _messages.forEach((item: any, i) => { | ||||||
|  |             if (item.id === msg.id){ | ||||||
|  |                 _messages[i] = msg | ||||||
|  |                 flag = true | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         //console.log(payload, flag)
 | ||||||
|  |         if (flag){ | ||||||
|  |             setMessages([..._messages]); | ||||||
|  |         } else { | ||||||
|  |             setMessages([msg, ..._messages]); | ||||||
|  |         } | ||||||
|  |         readMessages([msg.id]) | ||||||
|  | 
 | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const onOpponentRead = (payload: any) => { | ||||||
|  |         console.log('onOpponentRead', payload) | ||||||
|  |         const _messages = [... messages] as any[]; | ||||||
|  |         _messages.forEach((item: any, i) => { | ||||||
|  |             if (item.id === payload.messageId){ | ||||||
|  |                 _messages[i].seen = true | ||||||
|  | 
 | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |         setMessages([..._messages]) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         addListener('onConnected', onConnected) | ||||||
|  |         addListener('ReceiveMessage', onReceiveMessage) | ||||||
|  |         addListener('MessageWasRead', onOpponentRead) | ||||||
|  |     }, [messages, me, setMessages]); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     const handleSendMessages = () => { | ||||||
|  |         newMessage({ | ||||||
|  |             GroupId: groupId.toString(), | ||||||
|  |             CreatorId: me.id, | ||||||
|  |             Content: text | ||||||
|  |         }) | ||||||
|  |         setText('') | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const onEnterPress = (e: any) => { | ||||||
|  |         if(e.keyCode == 13 && e.shiftKey == false) { | ||||||
|  |             e.preventDefault(); | ||||||
|  |             handleSendMessages(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     const handleChangeMessage = (e: any) =>{ | ||||||
|  |         const { value } = e.target; | ||||||
|  |         e.preventDefault(); | ||||||
|  |         setText(value); | ||||||
|  |         return true | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     const messageRender = (message: any, i:number) => { | ||||||
|  |         const item = message.sentByMe ? me : notMe | ||||||
|  |         const date = dayjs(message.sentAt).fromNow() | ||||||
|  |         const imgSrc = item?.faceImage?.descriptor ? 'http://static.bbuddy.expert/' + item.faceImage.descriptor.split(':')[1] : '' | ||||||
|  |         return ( | ||||||
|  |             <div | ||||||
|  |                 className={message.sentByMe ? 'b-message__list b-message__list--me' : 'b-message__list'} | ||||||
|  |                 key={'message'+i} | ||||||
|  |             > | ||||||
|  |                 <div className="b-message__item "> | ||||||
|  |                     <div className="b-message__avatar"> | ||||||
|  |                         {imgSrc && (<img src={imgSrc} className="" alt=""/>)} | ||||||
|  |                     </div> | ||||||
|  |                     <div className="b-message__text" style={{minWidth: '150px'}}> | ||||||
|  |                         {message.text} | ||||||
|  |                         <span className="date"> | ||||||
|  |                             <span className="checks" style={{color: message.seen ? 'green' : 'blue'}}> | ||||||
|  |                             <CheckOutlined /> | ||||||
|  |                                 { message?.seen && (<CheckOutlined style={{marginLeft: '-10px'}} />)} | ||||||
|  |                             </span> | ||||||
|  |                             {date} | ||||||
|  |                         </span> | ||||||
|  |                     </div> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  |         ) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     return ( | ||||||
|  | 
 | ||||||
|  |             <div className="b-message"> | ||||||
|  |                 <Loader isLoading={loading}> | ||||||
|  |                 <div className="b-message__inner"> | ||||||
|  |                     {messages.map((el, i)=> (messageRender(el, i)))} | ||||||
|  |                 </div> | ||||||
|  |                 </Loader> | ||||||
|  |                 <div className="b-message__form"> | ||||||
|  |                     <textarea placeholder="Type your message here" onKeyDown={onEnterPress} | ||||||
|  |                               onChange={handleChangeMessage} value={text}/> | ||||||
|  |                     <button className="b-message__btn" type="submit" onClick={handleSendMessages}/> | ||||||
|  |                 </div> | ||||||
|  |             </div> | ||||||
|  | 
 | ||||||
|  |     ) | ||||||
|  | } | ||||||
|  | @ -15,6 +15,9 @@ import { getStorageValue } from '../../hooks/useLocalStorage'; | ||||||
| import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common'; | import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common'; | ||||||
| import { ScheduleModal } from '../Modals/ScheduleModal'; | import { ScheduleModal } from '../Modals/ScheduleModal'; | ||||||
| import { ScheduleModalResult } from '../Modals/ScheduleModalResult'; | import { ScheduleModalResult } from '../Modals/ScheduleModalResult'; | ||||||
|  | import SignalrConnection from '../../lib/signalr-connection'; | ||||||
|  | import { useRouter } from '../../navigation'; | ||||||
|  | import { useLocalStorage } from '../../hooks/useLocalStorage'; | ||||||
| 
 | 
 | ||||||
| type ExpertDetailsProps = { | type ExpertDetailsProps = { | ||||||
|     expert: ExpertDetails; |     expert: ExpertDetails; | ||||||
|  | @ -32,8 +35,29 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId }) | ||||||
|     const { publicCoachDetails } = expert || {}; |     const { publicCoachDetails } = expert || {}; | ||||||
|     const [showSchedulerModal, setShowSchedulerModal] = useState<boolean>(false); |     const [showSchedulerModal, setShowSchedulerModal] = useState<boolean>(false); | ||||||
|     const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data'); |     const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data'); | ||||||
|     const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] } } = expert || {}; |  | ||||||
|     const isRus = locale === Locale.ru; |     const isRus = locale === Locale.ru; | ||||||
|  |     const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] , id, botUserId} } = expert || {}; | ||||||
|  |     const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); | ||||||
|  |     const { joinChatPerson, closeConnection } = SignalrConnection(); | ||||||
|  |     const router = useRouter(); | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     useEffect(() => { | ||||||
|  |         document?.addEventListener('show_pay_form', handleShowPayForm); | ||||||
|  | 
 | ||||||
|  |         return () => { | ||||||
|  |             closeConnection(); | ||||||
|  |             document?.removeEventListener('show_pay_form', handleShowPayForm); | ||||||
|  |         } | ||||||
|  |     }, []); | ||||||
|  | 
 | ||||||
|  |     const handleJoinChat = (id?: number) => { | ||||||
|  |         if (id) { | ||||||
|  |             joinChatPerson(id).then((res: any) => { | ||||||
|  |                 router.push(`/account/messages/${res.id}` as string); | ||||||
|  |             }) | ||||||
|  |         } | ||||||
|  |     } | ||||||
| 
 | 
 | ||||||
|     const checkSession = (data?: SignupSessionData) => { |     const checkSession = (data?: SignupSessionData) => { | ||||||
|         if (data?.startAtUtc && data?.tagId) { |         if (data?.startAtUtc && data?.tagId) { | ||||||
|  | @ -44,7 +68,7 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId }) | ||||||
|             } else { |             } else { | ||||||
|                 setShowSchedulerModal(false); |                 setShowSchedulerModal(false); | ||||||
|                 const showAuth = new Event('show_auth_enter'); |                 const showAuth = new Event('show_auth_enter'); | ||||||
|                 document.dispatchEvent(showAuth); |                 document?.dispatchEvent(showAuth); | ||||||
|             } |             } | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  | @ -54,13 +78,6 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId }) | ||||||
|         setMode('pay'); |         setMode('pay'); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     useEffect(() => { |  | ||||||
|         document.addEventListener('show_pay_form', handleShowPayForm); |  | ||||||
|         return () => { |  | ||||||
|             document.removeEventListener('show_pay_form', handleShowPayForm); |  | ||||||
|         }; |  | ||||||
|     }, []); |  | ||||||
| 
 |  | ||||||
|     const onSchedulerHandle = () => { |     const onSchedulerHandle = () => { | ||||||
|         setMode('data'); |         setMode('data'); | ||||||
|         setShowSchedulerModal(true); |         setShowSchedulerModal(true); | ||||||
|  | @ -86,18 +103,26 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId }) | ||||||
|                         </div> |                         </div> | ||||||
|                     </div> |                     </div> | ||||||
|                 </div> |                 </div> | ||||||
|                 <div className="expert-card__wrap-btn"> |                 {jwt && ( | ||||||
|                     <Button className="btn-apply" onClick={onSchedulerHandle}> |                     <div className="expert-card__wrap-btn"> | ||||||
|                         <img src="/images/calendar-outline.svg" className="" alt="" /> |                         <Button className="btn-apply" onClick={() => handleJoinChat(id)}> | ||||||
|                         {i18nText('schedule', locale)} |                             {i18nText('chat.join', locale)} | ||||||
|                     </Button> |                         </Button> | ||||||
|                     {/* |                         <Button | ||||||
|                         <a href="#" className="btn-video"> |                             className={`btn-apply${!botUserId ? ' btn-disabled' : ''}`} | ||||||
|                             <img src="/images/videocam-outline.svg" className="" alt=""/> |                             disabled={!botUserId} | ||||||
|                             Video |                             onClick={botUserId ? () => handleJoinChat(botUserId) : undefined} | ||||||
|                         </a> |                         > | ||||||
|                     */} |                             {i18nText('chat.joinAI', locale)} | ||||||
|                 </div> |                         </Button> | ||||||
|  |                         {/* | ||||||
|  |                             <a href="#" className="btn-video"> | ||||||
|  |                                 <img src="/images/videocam-outline.svg" className="" alt=""/> | ||||||
|  |                                 Video | ||||||
|  |                             </a> | ||||||
|  |                         */} | ||||||
|  |                     </div> | ||||||
|  |                 )} | ||||||
|             </div> |             </div> | ||||||
|             <div className="expert-info"> |             <div className="expert-info"> | ||||||
|                 {/* <h2 className="title-h2">{}</h2> */} |                 {/* <h2 className="title-h2">{}</h2> */} | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung', |     sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung', | ||||||
|     successPayment: 'Erfolgreiche Zahlung', |     successPayment: 'Erfolgreiche Zahlung', | ||||||
|     errorPayment: 'Zahlungsfehler', |     errorPayment: 'Zahlungsfehler', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Chat starten', | ||||||
|  |         joinAI: 'Start AI chat' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'Die E-Mail-Adresse ist ungültig', |         invalidEmail: 'Die E-Mail-Adresse ist ungültig', | ||||||
|         emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', |         emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Write your wishes about the session', |     sessionWishes: 'Write your wishes about the session', | ||||||
|     successPayment: 'Successful Payment', |     successPayment: 'Successful Payment', | ||||||
|     errorPayment: 'Payment Error', |     errorPayment: 'Payment Error', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Start chat', | ||||||
|  |         joinAI: 'Start AI chat' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'The email address is not valid', |         invalidEmail: 'The email address is not valid', | ||||||
|         emptyEmail: 'Please enter your E-mail', |         emptyEmail: 'Please enter your E-mail', | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Escribe tus deseos sobre la sesión', |     sessionWishes: 'Escribe tus deseos sobre la sesión', | ||||||
|     successPayment: 'Pago Exitoso', |     successPayment: 'Pago Exitoso', | ||||||
|     errorPayment: 'Error de Pago', |     errorPayment: 'Error de Pago', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Empezar un chat', | ||||||
|  |         joinAI: 'Start AI chat' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'La dirección de correo electrónico no es válida', |         invalidEmail: 'La dirección de correo electrónico no es válida', | ||||||
|         emptyEmail: 'Introduce tu correo electrónico', |         emptyEmail: 'Introduce tu correo electrónico', | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Écrivez vos souhaits concernant la session', |     sessionWishes: 'Écrivez vos souhaits concernant la session', | ||||||
|     successPayment: 'Paiement Réussi', |     successPayment: 'Paiement Réussi', | ||||||
|     errorPayment: 'Erreur de Paiement', |     errorPayment: 'Erreur de Paiement', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Commencer la discussion', | ||||||
|  |         joinAI: 'Start AI chat' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'L\'adresse e-mail n\'est pas valide', |         invalidEmail: 'L\'adresse e-mail n\'est pas valide', | ||||||
|         emptyEmail: 'Veuillez saisir votre e-mail', |         emptyEmail: 'Veuillez saisir votre e-mail', | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione', |     sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione', | ||||||
|     successPayment: 'Pagamento Riuscito', |     successPayment: 'Pagamento Riuscito', | ||||||
|     errorPayment: 'Errore di Pagamento', |     errorPayment: 'Errore di Pagamento', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Avvia chat', | ||||||
|  |         joinAI: 'Start AI chat' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'L\'indirizzo e-mail non è valido', |         invalidEmail: 'L\'indirizzo e-mail non è valido', | ||||||
|         emptyEmail: 'Inserisci l\'e-mail', |         emptyEmail: 'Inserisci l\'e-mail', | ||||||
|  |  | ||||||
|  | @ -185,6 +185,10 @@ export default { | ||||||
|     sessionWishes: 'Напишите свои пожелания по поводу сессии', |     sessionWishes: 'Напишите свои пожелания по поводу сессии', | ||||||
|     successPayment: 'Успешная оплата', |     successPayment: 'Успешная оплата', | ||||||
|     errorPayment: 'Ошибка оплаты', |     errorPayment: 'Ошибка оплаты', | ||||||
|  |     chat: { | ||||||
|  |         join: 'Начать чат', | ||||||
|  |         joinAI: 'Начать чат с ИИ' | ||||||
|  |     }, | ||||||
|     errors: { |     errors: { | ||||||
|         invalidEmail: 'Адрес электронной почты недействителен', |         invalidEmail: 'Адрес электронной почты недействителен', | ||||||
|         emptyEmail: 'Пожалуйста, введите ваш E-mail', |         emptyEmail: 'Пожалуйста, введите ваш E-mail', | ||||||
|  |  | ||||||
|  | @ -0,0 +1,79 @@ | ||||||
|  | import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr'; | ||||||
|  | import { IHttpConnectionOptions } from "@microsoft/signalr/src/IHttpConnectionOptions"; | ||||||
|  | import { AUTH_TOKEN_KEY, BASE_URL } from '../constants/common'; | ||||||
|  | import { IChatMessage } from '../types/chat'; | ||||||
|  | 
 | ||||||
|  | const chatMessageMethodName = 'ReceiveMessage'; | ||||||
|  | const chatUserStatusChangeMethodName = 'chatUserStatusChange'; | ||||||
|  | const chatSeenMethodName = 'MessageWasRead'; | ||||||
|  | const chatTypingMethodName = 'directTyping'; | ||||||
|  | const sendChatTextMethodName = 'SendMessage'; | ||||||
|  | const sendChatTypingMethodName = 'DirectIsTyping'; | ||||||
|  | const serverError = 'Error'; | ||||||
|  | 
 | ||||||
|  | const sendChatSeenMethodName = 'ConfirmMessageRead'; | ||||||
|  | const sendChatMessagesSeenMethodName = 'ConfirmMessagesRead'; | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | const joinChatMethodName = 'JoinChat'; | ||||||
|  | const leaveChatMethodName = 'LeaveChat'; | ||||||
|  | 
 | ||||||
|  | const joinChatsMethodName = 'JoinChats'; | ||||||
|  | const leaveChatsMethodName = 'LeaveChats'; | ||||||
|  | const chatsReceiveMessageMethodName = 'ChatsMessageCreated'; | ||||||
|  | 
 | ||||||
|  | class SignalConnector { | ||||||
|  |     private connection: HubConnection; | ||||||
|  |     private events ={} as any; | ||||||
|  |     static instance: SignalConnector; | ||||||
|  |     constructor() { | ||||||
|  |         const options = { | ||||||
|  |             accessTokenFactory: () => localStorage.getItem(AUTH_TOKEN_KEY) | ||||||
|  |         } as IHttpConnectionOptions; | ||||||
|  |         this.connection = new HubConnectionBuilder() | ||||||
|  |             .withUrl(`${BASE_URL}/hubs/chat`, options) | ||||||
|  |             .withAutomaticReconnect() | ||||||
|  |             .configureLogging(LogLevel.Debug) | ||||||
|  |             .build(); | ||||||
|  |         this.connection.start().then(()=>{ | ||||||
|  |             this.events?.onConnected(true) | ||||||
|  |             for (const k in this.events) { | ||||||
|  |                 if (k != 'onConnected'){ | ||||||
|  |                     this.connection.on(k, (payload: any) => { | ||||||
|  |                         this.events[k](payload); | ||||||
|  |                     }); | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |         }).catch(err => console.log('SignalR Error', err)); | ||||||
|  |     } | ||||||
|  |     public addListener = (name: string, func: any)=> { | ||||||
|  |         this.events[name] = func; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public closeConnection = () => { | ||||||
|  |         this.connection.stop(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public newMessage = (message: IChatMessage) => { | ||||||
|  |         this.connection.invoke(sendChatTextMethodName, message).then(x => console.log('NewMsg',x)) | ||||||
|  |     } | ||||||
|  |     public joinChat = (groupId: number) => { | ||||||
|  |         this.connection.invoke(joinChatMethodName, groupId, null).then(x => console.log(joinChatMethodName, x)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public joinChatPerson = (accId: number) => { | ||||||
|  |         return this.connection.invoke(joinChatMethodName, 0, accId) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public readMessages = (messagesId: number[]) => { | ||||||
|  |         this.connection.invoke(sendChatMessagesSeenMethodName, messagesId).then(x => console.log(sendChatMessagesSeenMethodName, x)) | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static getInstance(): SignalConnector { | ||||||
|  |         if (!SignalConnector.instance) | ||||||
|  |             SignalConnector.instance = new SignalConnector(); | ||||||
|  |         return SignalConnector.instance; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export default SignalConnector.getInstance; | ||||||
|  | @ -563,6 +563,10 @@ a { | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | .btn-disabled { | ||||||
|  |   opacity: .4 !important; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| .btn-back { | .btn-back { | ||||||
|   user-select: none; |   user-select: none; | ||||||
|   outline: none; |   outline: none; | ||||||
|  | @ -823,6 +827,7 @@ a { | ||||||
|     flex: 0 0 100%; |     flex: 0 0 100%; | ||||||
|     display: flex; |     display: flex; | ||||||
|     gap: 16px; |     gap: 16px; | ||||||
|  |     flex-direction: column; | ||||||
| 
 | 
 | ||||||
|     .btn-apply, |     .btn-apply, | ||||||
|     .btn-video { |     .btn-video { | ||||||
|  |  | ||||||
|  | @ -13,6 +13,8 @@ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   &__inner { |   &__inner { | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column-reverse; | ||||||
|     flex-grow: 1; |     flex-grow: 1; | ||||||
|     height: 0; |     height: 0; | ||||||
|     position: relative; |     position: relative; | ||||||
|  | @ -94,7 +96,8 @@ | ||||||
|     align-items: center; |     align-items: center; | ||||||
| 
 | 
 | ||||||
|     textarea { |     textarea { | ||||||
|       width: calc(100% - 136px); |       resize: none; | ||||||
|  |       width: calc(100% - 40px); | ||||||
|       height: 24px; |       height: 24px; | ||||||
|       padding: 0 8px; |       padding: 0 8px; | ||||||
|       border-radius: 0; |       border-radius: 0; | ||||||
|  |  | ||||||
|  | @ -0,0 +1,12 @@ | ||||||
|  | 
 | ||||||
|  | export interface IChatMessage { | ||||||
|  |     GroupId: string; | ||||||
|  |     CreatorId: number; | ||||||
|  |     Content: string; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | export interface IChatJoin { | ||||||
|  |     GroupId: string; | ||||||
|  |     CreatorId: number; | ||||||
|  |     Content: string; | ||||||
|  | } | ||||||
|  | @ -37,6 +37,7 @@ export type ThemeGroup = { | ||||||
| 
 | 
 | ||||||
| export interface ExpertItem { | export interface ExpertItem { | ||||||
|     id: number; |     id: number; | ||||||
|  |     botUserId?: number; | ||||||
|     name: string; |     name: string; | ||||||
|     surname?: string; |     surname?: string; | ||||||
|     faceImageUrl?: string; |     faceImageUrl?: string; | ||||||
|  |  | ||||||
|  | @ -5,8 +5,7 @@ import { i18nText } from '../i18nKeys'; | ||||||
| const ROUTES = ['sessions', 'rooms', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile']; | const ROUTES = ['sessions', 'rooms', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile']; | ||||||
| const COUNTS: Record<string, number> = { | const COUNTS: Record<string, number> = { | ||||||
|     sessions: 12, |     sessions: 12, | ||||||
|     notifications: 5, |     notifications: 5 | ||||||
|     messages: 113 |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({ | export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({ | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue