feat: add sessions page, add delete modal, refactoring
This commit is contained in:
parent
ffbbeadb40
commit
c2e29cbef3
|
@ -11,6 +11,7 @@
|
||||||
"@ant-design/cssinjs": "^1.18.1",
|
"@ant-design/cssinjs": "^1.18.1",
|
||||||
"@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",
|
||||||
|
"agora-rtc-react": "^2.1.0",
|
||||||
"antd": "^5.12.1",
|
"antd": "^5.12.1",
|
||||||
"antd-img-crop": "^4.21.0",
|
"antd-img-crop": "^4.21.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
@ -873,6 +874,17 @@
|
||||||
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/agora-rtc-react": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agora-rtc-react/-/agora-rtc-react-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-3FGteA7FG51oK5MusbYNgAcKZaAQK+4sbEz4F0DPzcpDxqNANpocJDqOsmXoUAj5yDBsBZelmagU3abd++6RGA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=10"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ajv": {
|
"node_modules/ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
@ -5947,6 +5959,12 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {}
|
"requires": {}
|
||||||
},
|
},
|
||||||
|
"agora-rtc-react": {
|
||||||
|
"version": "2.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/agora-rtc-react/-/agora-rtc-react-2.1.0.tgz",
|
||||||
|
"integrity": "sha512-3FGteA7FG51oK5MusbYNgAcKZaAQK+4sbEz4F0DPzcpDxqNANpocJDqOsmXoUAj5yDBsBZelmagU3abd++6RGA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"ajv": {
|
"ajv": {
|
||||||
"version": "6.12.6",
|
"version": "6.12.6",
|
||||||
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"@ant-design/cssinjs": "^1.18.1",
|
"@ant-design/cssinjs": "^1.18.1",
|
||||||
"@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",
|
||||||
|
"agora-rtc-react": "^2.1.0",
|
||||||
"antd": "^5.12.1",
|
"antd": "^5.12.1",
|
||||||
"antd-img-crop": "^4.21.0",
|
"antd-img-crop": "^4.21.0",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
|
|
@ -73,3 +73,16 @@ export const getRecentSessions = (locale: string, jwt: string, filter?: Sessions
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const getSessionDetails = (locale: string, jwt: string, id: number): Promise<AxiosResponse<Session>> => (
|
||||||
|
apiClient.post(
|
||||||
|
'/home/session',
|
||||||
|
{ id },
|
||||||
|
{
|
||||||
|
headers: {
|
||||||
|
'X-User-Language': locale,
|
||||||
|
Authorization: `Bearer ${jwt}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import React, { Suspense } from 'react';
|
import React, { Suspense } from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { SessionsTabs } from '../../../../../components/Account';
|
import { SessionsAll } from '../../../../../components/Account';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Bbuddy - Account - Sessions',
|
title: 'Bbuddy - Account - Sessions',
|
||||||
|
@ -13,9 +13,7 @@ export default function Sessions({ params: { locale } }: { params: { locale: str
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Suspense fallback={<p>Loading...</p>}>
|
<Suspense fallback={<p>Loading...</p>}>
|
||||||
<SessionsTabs
|
<SessionsAll locale={locale} />
|
||||||
locale={locale}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
</Suspense>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useState } from 'react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
|
||||||
|
@ -8,6 +8,7 @@ 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 } from '../../hooks/useLocalStorage';
|
||||||
import { i18nText } from '../../i18nKeys';
|
import { i18nText } from '../../i18nKeys';
|
||||||
|
import { DeleteAccountModal } from '../Modals/DeleteAccountModal';
|
||||||
|
|
||||||
const Logout = styled(Button)`
|
const Logout = styled(Button)`
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -24,6 +25,7 @@ export const AccountMenu = ({ menu, locale }: { menu: { path: string, title: str
|
||||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||||
const pathname = selectedLayoutSegment || '';
|
const pathname = selectedLayoutSegment || '';
|
||||||
const paths = usePathname();
|
const paths = usePathname();
|
||||||
|
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
|
||||||
|
|
||||||
const onLogout = () => {
|
const onLogout = () => {
|
||||||
deleteStorageKey(AUTH_TOKEN_KEY);
|
deleteStorageKey(AUTH_TOKEN_KEY);
|
||||||
|
@ -31,9 +33,7 @@ export const AccountMenu = ({ menu, locale }: { menu: { path: string, title: str
|
||||||
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onDeleteAccount = () => {
|
const onDeleteAccount = () => setShowDeleteModal(true);
|
||||||
console.log('delete');
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className="list-sidebar">
|
<ul className="list-sidebar">
|
||||||
|
@ -62,6 +62,10 @@ export const AccountMenu = ({ menu, locale }: { menu: { path: string, title: str
|
||||||
>
|
>
|
||||||
{i18nText('deleteAcc', locale)}
|
{i18nText('deleteAcc', locale)}
|
||||||
</Logout>
|
</Logout>
|
||||||
|
<DeleteAccountModal
|
||||||
|
open={showDeleteModal}
|
||||||
|
handleCancel={() => setShowDeleteModal(false)}
|
||||||
|
/>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
export { AccountMenu } from './AccountMenu';
|
export { AccountMenu } from './AccountMenu';
|
||||||
export { SessionsTabs } from './SessionsTabs';
|
|
||||||
export { ProfileSettings } from './ProfileSettings';
|
export { ProfileSettings } from './ProfileSettings';
|
||||||
|
export * from './sessions';
|
||||||
|
|
|
@ -0,0 +1,137 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useCallback, useEffect, useState } from 'react';
|
||||||
|
import { Tag } from 'antd';
|
||||||
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import Image from 'next/image';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
import { Link } from '../../../navigation';
|
||||||
|
import { i18nText } from '../../../i18nKeys';
|
||||||
|
import { getDuration, getPrice } from '../../../utils/expert';
|
||||||
|
import { Session } from '../../../types/sessions';
|
||||||
|
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
|
||||||
|
import { getSessionDetails } from '../../../actions/profile';
|
||||||
|
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||||
|
import { Loader } from '../../view/Loader';
|
||||||
|
|
||||||
|
type SessionDetailsProps = {
|
||||||
|
locale: string;
|
||||||
|
sessionId: number;
|
||||||
|
goBack: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SessionDetails = ({ sessionId, locale, goBack }: SessionDetailsProps) => {
|
||||||
|
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||||
|
const [userId] = useLocalStorage(AUTH_USER, '');
|
||||||
|
const [loading, setLoading] = useState<boolean>(false);
|
||||||
|
const [errorData, setErrorData] = useState<any>();
|
||||||
|
const [session, setSession] = useState<Session>();
|
||||||
|
|
||||||
|
const fetchData = useCallback(() => {
|
||||||
|
setLoading(true);
|
||||||
|
setErrorData(undefined);
|
||||||
|
setSession(undefined);
|
||||||
|
|
||||||
|
getSessionDetails(locale, jwt, sessionId)
|
||||||
|
.then(({ data }) => {
|
||||||
|
console.log(data);
|
||||||
|
setSession(data);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
setErrorData(err);
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
setLoading(false);
|
||||||
|
})
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchData();
|
||||||
|
}, [sessionId]);
|
||||||
|
|
||||||
|
const client = session?.clients?.length ? session?.clients[0] : null;
|
||||||
|
const current = +userId !== client?.id ? client : session?.coach;
|
||||||
|
const startDate = session?.scheduledStartAtUtc ? dayjs(session?.scheduledStartAtUtc).locale(locale) : null;
|
||||||
|
const endDate = session?.scheduledEndAtUtc ? dayjs(session?.scheduledEndAtUtc).locale(locale) : null;
|
||||||
|
const today = startDate ? dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD') : false;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Loader
|
||||||
|
isLoading={loading}
|
||||||
|
errorData={errorData}
|
||||||
|
refresh={fetchData}
|
||||||
|
>
|
||||||
|
<div className="card-detail">
|
||||||
|
<div>
|
||||||
|
<button onClick={goBack}>back</button>
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__expert">
|
||||||
|
<div className="card-detail__portrait">
|
||||||
|
<Image src={current?.faceImageUrl || '/images/person.png'} width={140} height={140} alt="" />
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__inner">
|
||||||
|
<Link href={`/experts/${current?.id}` as any} target="_blank">
|
||||||
|
<div className="card-detail__name">{`${current?.name} ${current?.surname || ''}`}</div>
|
||||||
|
</Link>
|
||||||
|
<div className="card-detail__info">
|
||||||
|
<div className="card-detail__lang">
|
||||||
|
{/* current?.coachLanguages?.map((lang) => (
|
||||||
|
<Tag key={lang} className="skills__list__item">{lang}</Tag>
|
||||||
|
)) */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="card-profile__title">{current?.speciality}</div> */}
|
||||||
|
<div className="card-detail__coast">
|
||||||
|
{getPrice(session?.cost)} <span>/ {getDuration(locale, session?.totalDuration)}</span>
|
||||||
|
</div>
|
||||||
|
<div className={`card-detail__date${today ? ' chosen': ''}`}>
|
||||||
|
{today
|
||||||
|
? `${i18nText('today', locale)} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`
|
||||||
|
: `${startDate.format('D MMMM')} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`}
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__skills">
|
||||||
|
<div className="skills__list">
|
||||||
|
{session?.themesTags?.slice(0, 2).map((skill) => <Tag key={skill?.id} className="skills__list__item">{skill?.name}</Tag>)}
|
||||||
|
{session?.themesTags?.length > 2
|
||||||
|
? (
|
||||||
|
<Tag className="skills__list__more">
|
||||||
|
<Link href={`/experts/${current?.id}` as any} target="_blank">
|
||||||
|
{`+${session?.themesTags?.length - 2}`}
|
||||||
|
</Link>
|
||||||
|
</Tag>
|
||||||
|
) : null }
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{/* <div className="card-profile__subtitle">{current?.specialityDesc}</div>
|
||||||
|
<div className="card-profile__desc">{current?.description}</div> */}
|
||||||
|
<Link href={`/experts/${current?.id}` as any} target="_blank">
|
||||||
|
{i18nText('details', locale)}
|
||||||
|
<RightOutlined style={{ fontSize: '10px', padding: '0 7px' }}/>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__actions">
|
||||||
|
<button>Start Session</button>
|
||||||
|
<button>Decline Session</button>
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__comments">
|
||||||
|
<div className="card-detail__comments_header">
|
||||||
|
<div className="card-detail__comments_title">
|
||||||
|
My Comments
|
||||||
|
</div>
|
||||||
|
<button>Add new</button>
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__comments_item">
|
||||||
|
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__comments_title">
|
||||||
|
Coach Comments
|
||||||
|
</div>
|
||||||
|
<div className="card-detail__comments_item">
|
||||||
|
Sed tincidunt finibus eros nec feugiat. Nulla facilisi. Nunc maximus magna et egestas tincidunt. Integer lobortis laoreet neque at sodales. Aenean eget risus pharetra, efficitur dolor ut, commodo lacus. Sed vitae nunc odio. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis et velit et dolor rutrum euismod a pretium est.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Loader>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useCallback, useEffect, useState } from 'react';
|
import React, { useCallback, useEffect, useState, MouseEvent } from 'react';
|
||||||
import { Empty, Space } from 'antd';
|
import { Empty, Space } from 'antd';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
import 'dayjs/locale/ru';
|
import 'dayjs/locale/ru';
|
||||||
|
@ -9,14 +9,19 @@ import 'dayjs/locale/de';
|
||||||
import 'dayjs/locale/it';
|
import 'dayjs/locale/it';
|
||||||
import 'dayjs/locale/fr';
|
import 'dayjs/locale/fr';
|
||||||
import 'dayjs/locale/es';
|
import 'dayjs/locale/es';
|
||||||
import { Loader } from '../view/Loader';
|
import { Loader } from '../../view/Loader';
|
||||||
import { useLocalStorage } from '../../hooks/useLocalStorage';
|
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||||
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
|
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
|
||||||
import { getRecentSessions, getRequestedSessions, getUpcomingSessions } from '../../actions/profile';
|
import { getRecentSessions, getRequestedSessions, getUpcomingSessions } from '../../../actions/profile';
|
||||||
import { Session, Sessions, SessionType } from '../../types/sessions';
|
import { Session, Sessions, SessionType } from '../../../types/sessions';
|
||||||
import { i18nText } from '../../i18nKeys';
|
import { i18nText } from '../../../i18nKeys';
|
||||||
|
|
||||||
export const SessionsTabs = ({ locale }: { locale: string }) => {
|
type SessionsTabsProps = {
|
||||||
|
locale: string;
|
||||||
|
updateSession: (val: number) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SessionsTabs = ({ locale, updateSession }: SessionsTabsProps) => {
|
||||||
const [activeTab, setActiveTab] = useState<number>(0);
|
const [activeTab, setActiveTab] = useState<number>(0);
|
||||||
const [sort, setSort] = useState<string>();
|
const [sort, setSort] = useState<string>();
|
||||||
const [sessions, setSessions] = useState<Sessions>();
|
const [sessions, setSessions] = useState<Sessions>();
|
||||||
|
@ -56,6 +61,12 @@ export const SessionsTabs = ({ locale }: { locale: string }) => {
|
||||||
setSort(value);
|
setSort(value);
|
||||||
}, [sort]);
|
}, [sort]);
|
||||||
|
|
||||||
|
const onClickSession = (event: MouseEvent<HTMLDivElement>, id: number) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
event.preventDefault();
|
||||||
|
updateSession(id);
|
||||||
|
};
|
||||||
|
|
||||||
const getChildren = (list?: Session[]) => (
|
const getChildren = (list?: Session[]) => (
|
||||||
<>
|
<>
|
||||||
{/* <div className="filter-session">
|
{/* <div className="filter-session">
|
||||||
|
@ -82,7 +93,7 @@ export const SessionsTabs = ({ locale }: { locale: string }) => {
|
||||||
const today = dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD');
|
const today = dayjs().format('YYYY-MM-DD') === startDate.format('YYYY-MM-DD');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id} className="card-profile session__item">
|
<div key={id} className="card-profile session__item" onClick={(e: MouseEvent<HTMLDivElement>) => onClickSession(e, id)}>
|
||||||
<div className="card-profile__header">
|
<div className="card-profile__header">
|
||||||
<div className="card-profile__header__portrait">
|
<div className="card-profile__header__portrait">
|
||||||
<img src={current?.faceImageUrl || '/images/person.png'} className="" alt="" />
|
<img src={current?.faceImageUrl || '/images/person.png'} className="" alt="" />
|
|
@ -0,0 +1,22 @@
|
||||||
|
'use client'
|
||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { SessionDetails } from './SessionDetails';
|
||||||
|
import { SessionsTabs } from './SessionsTabs';
|
||||||
|
|
||||||
|
export const SessionsAll = ({ locale }: { locale: string }) => {
|
||||||
|
const [customSession, setCustomSession] = useState<number | undefined>();
|
||||||
|
|
||||||
|
return customSession ? (
|
||||||
|
<SessionDetails
|
||||||
|
locale={locale}
|
||||||
|
sessionId={customSession}
|
||||||
|
goBack={() => setCustomSession(undefined)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<SessionsTabs
|
||||||
|
locale={locale}
|
||||||
|
updateSession={setCustomSession}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
|
@ -9,6 +9,7 @@ import Image from 'next/image';
|
||||||
import { Link, useRouter } from '../../navigation';
|
import { Link, useRouter } from '../../navigation';
|
||||||
import { ExpertsData, Filter, GeneralFilter } from '../../types/experts';
|
import { ExpertsData, Filter, GeneralFilter } from '../../types/experts';
|
||||||
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
|
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
|
||||||
|
import { getDuration, getPrice } from '../../utils/expert';
|
||||||
import { getExpertsList } from '../../actions/experts';
|
import { getExpertsList } from '../../actions/experts';
|
||||||
import { CustomPagination } from '../view/CustomPagination';
|
import { CustomPagination } from '../view/CustomPagination';
|
||||||
import { CustomSpin } from '../view/CustomSpin';
|
import { CustomSpin } from '../view/CustomSpin';
|
||||||
|
@ -31,8 +32,6 @@ export const ExpertsList = ({
|
||||||
}: ExpertListProps) => {
|
}: ExpertListProps) => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const getDuration = (value?: any): string => `${value || 0}${locale === 'ru' ? 'мин' : 'min'}`;
|
|
||||||
const getPrice = (value?: any): string => `${value || 0}€`;
|
|
||||||
const [experts, setExperts] = useState<ExpertsData | undefined>();
|
const [experts, setExperts] = useState<ExpertsData | undefined>();
|
||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
|
||||||
|
@ -102,7 +101,7 @@ export const ExpertsList = ({
|
||||||
<div className="card-profile__header__name">{`${item.name} ${item?.surname || ''}`}</div>
|
<div className="card-profile__header__name">{`${item.name} ${item?.surname || ''}`}</div>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="card-profile__header__price">
|
<div className="card-profile__header__price">
|
||||||
{getPrice(item?.sessionCost)} <span>/ {getDuration(item?.sessionDuration)}</span>
|
{getPrice(item?.sessionCost)} <span>/ {getDuration(locale, item?.sessionDuration)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="card-profile__header__lang">
|
<div className="card-profile__header__lang">
|
||||||
|
|
|
@ -3,9 +3,8 @@
|
||||||
import React, { Dispatch, FC, SetStateAction, useEffect } from 'react';
|
import React, { Dispatch, FC, SetStateAction, useEffect } from 'react';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { Modal as AntdModal, Form } from 'antd';
|
import { Modal, Form } from 'antd';
|
||||||
import { CloseOutlined } from '@ant-design/icons';
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
import { styled } from 'styled-components';
|
|
||||||
import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent';
|
import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent';
|
||||||
|
|
||||||
type AuthModalProps = {
|
type AuthModalProps = {
|
||||||
|
@ -16,25 +15,6 @@ type AuthModalProps = {
|
||||||
updateToken: string | Dispatch<SetStateAction<string | undefined>> | undefined;
|
updateToken: string | Dispatch<SetStateAction<string | undefined>> | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
const Modal = styled(AntdModal)`
|
|
||||||
.ant-modal-content {
|
|
||||||
border-radius: 24px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ant-modal-close {
|
|
||||||
height: 64px !important;
|
|
||||||
width: 64px !important;
|
|
||||||
border-radius: 50% !important;
|
|
||||||
background: #fff !important;
|
|
||||||
top: -32px !important;
|
|
||||||
inset-inline-end: -32px !important;
|
|
||||||
|
|
||||||
&:active, &:hover {
|
|
||||||
background: #fff !important;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export const AuthModal: FC<AuthModalProps> = ({
|
export const AuthModal: FC<AuthModalProps> = ({
|
||||||
open,
|
open,
|
||||||
handleCancel,
|
handleCancel,
|
||||||
|
@ -63,6 +43,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal
|
<Modal
|
||||||
|
className="b-modal"
|
||||||
open={open}
|
open={open}
|
||||||
title={undefined}
|
title={undefined}
|
||||||
onOk={undefined}
|
onOk={undefined}
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
'use client';
|
||||||
|
|
||||||
|
import React, { FC } from 'react';
|
||||||
|
import { Modal } from 'antd';
|
||||||
|
import { CloseOutlined } from '@ant-design/icons';
|
||||||
|
import Link from 'next/link';
|
||||||
|
|
||||||
|
type DeleteAccountModalProps = {
|
||||||
|
open: boolean;
|
||||||
|
handleCancel: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DeleteAccountModal: FC<DeleteAccountModalProps> = ({
|
||||||
|
open,
|
||||||
|
handleCancel
|
||||||
|
}) => (
|
||||||
|
<Modal
|
||||||
|
className="b-modal"
|
||||||
|
open={open}
|
||||||
|
title="Account deletion instructions"
|
||||||
|
onOk={undefined}
|
||||||
|
onCancel={handleCancel}
|
||||||
|
footer={false}
|
||||||
|
width={498}
|
||||||
|
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||||
|
>
|
||||||
|
<div className="b-modal__content">
|
||||||
|
<p>
|
||||||
|
To delete your BBUDDY account, please send an email requesting account deletion to the following email address:
|
||||||
|
</p>
|
||||||
|
<Link href="mailto:info@bbuddy.expert">info@bbuddy.expert</Link>
|
||||||
|
<p>
|
||||||
|
Upon receiving the request, we will delete all existing data associated with your account within 24 hours.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
);
|
|
@ -586,7 +586,6 @@ a {
|
||||||
line-height: 160%;
|
line-height: 160%;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
.card-profile {
|
.card-profile {
|
||||||
display: flex !important;
|
display: flex !important;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
|
@ -598,6 +597,7 @@ a {
|
||||||
&.session {
|
&.session {
|
||||||
&__item {
|
&__item {
|
||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
.b-modal {
|
||||||
|
.ant-modal-content {
|
||||||
|
border-radius: 24px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-modal-close {
|
||||||
|
height: 64px !important;
|
||||||
|
width: 64px !important;
|
||||||
|
border-radius: 50% !important;
|
||||||
|
background: #fff !important;
|
||||||
|
top: -32px !important;
|
||||||
|
inset-inline-end: -32px !important;
|
||||||
|
|
||||||
|
&:active, &:hover {
|
||||||
|
background: #fff !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
p {
|
||||||
|
margin: 16px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -767,9 +767,9 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 6px 8px 4px 8px;
|
padding: 6px 8px 4px;
|
||||||
border-radius: 16px;
|
border-radius: 16px;
|
||||||
background: #66A5AD;
|
background: #FFBD00;
|
||||||
color: $white;
|
color: $white;
|
||||||
@include rem(12);
|
@include rem(12);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
|
|
|
@ -0,0 +1,149 @@
|
||||||
|
.card-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
&__expert {
|
||||||
|
border-block-end: 1px solid #C4DFE6;
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 0 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__portrait {
|
||||||
|
width: 140px;
|
||||||
|
height: 140px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 2px solid #FFF;
|
||||||
|
background: lightgray 50%;
|
||||||
|
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__inner {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
flex: 1 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__comments {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
|
||||||
|
&_header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_title {
|
||||||
|
color: #6FB98F;
|
||||||
|
@include rem(18);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 150%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&_item {
|
||||||
|
padding: 16px;
|
||||||
|
background: #E4F5FA;
|
||||||
|
border-radius: 0 16px 16px 16px;
|
||||||
|
color: #66A5AD;
|
||||||
|
@include rem(13);
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 133.333%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
align-items: center !important;
|
||||||
|
gap: 16px;
|
||||||
|
align-self: stretch;
|
||||||
|
margin-block-end: 0 !important;
|
||||||
|
|
||||||
|
img {
|
||||||
|
object-fit: cover;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__name {
|
||||||
|
overflow: hidden;
|
||||||
|
color: #003B46;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
@include rem(18);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 133.333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__price {
|
||||||
|
color: #6FB98F;
|
||||||
|
@include rem(15);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 120%;
|
||||||
|
|
||||||
|
span {
|
||||||
|
color: #B7B7B7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
color: #003B46;
|
||||||
|
@include rem(18);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 133.333%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__subtitle {
|
||||||
|
color: #003B46;
|
||||||
|
@include rem(15);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 120%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__desc {
|
||||||
|
color: #66A5AD;
|
||||||
|
@include rem(13);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 123.077%;
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #FF8A00 !important;
|
||||||
|
@include rem(15);
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 160%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
@import "_details.scss";
|
|
@ -14,8 +14,10 @@
|
||||||
@import "_form.scss";
|
@import "_form.scss";
|
||||||
@import "_message.scss";
|
@import "_message.scss";
|
||||||
@import "_auth-modal.scss";
|
@import "_auth-modal.scss";
|
||||||
|
@import "_modal.scss";
|
||||||
|
|
||||||
@import "./view/style.scss";
|
@import "./view/style.scss";
|
||||||
|
@import "./sessions/style.scss";
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
type User = {
|
import { Language } from './tags';
|
||||||
|
|
||||||
|
type PublicUser = {
|
||||||
id: number;
|
id: number;
|
||||||
login?: string;
|
login?: string;
|
||||||
name?: string;
|
name?: string;
|
||||||
|
@ -6,6 +8,88 @@ type User = {
|
||||||
faceImageUrl?: string;
|
faceImageUrl?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// type User = {
|
||||||
|
// "id": 0,
|
||||||
|
// "login": "string",
|
||||||
|
// "password": "string",
|
||||||
|
// "role": "coach",
|
||||||
|
// "name": "string",
|
||||||
|
// "surname": "string",
|
||||||
|
// "phone": "string",
|
||||||
|
// "faceImageId": 0,
|
||||||
|
// "createdAtUtc": "2024-05-15T14:26:19.310Z",
|
||||||
|
// "practiceHours": 0,
|
||||||
|
// "supervisionPerYearId": 0,
|
||||||
|
// "sessionDuration": 0,
|
||||||
|
// "sessionCost": 0,
|
||||||
|
// "adminRoles": "admin",
|
||||||
|
// "isCoachApproved": true,
|
||||||
|
// "isBanned": true,
|
||||||
|
// "faceImage": {
|
||||||
|
// "id": 0,
|
||||||
|
// "userId": 0,
|
||||||
|
// "descriptor": "string",
|
||||||
|
// "fileType": "string",
|
||||||
|
// "contentLength": 0,
|
||||||
|
// "state": "active",
|
||||||
|
// "createdAtUtc": "2024-05-15T14:26:19.310Z",
|
||||||
|
// "unusedSinceUtc": "2024-05-15T14:26:19.310Z"
|
||||||
|
// },
|
||||||
|
// "coachRating": 0,
|
||||||
|
// "isTestMode": true,
|
||||||
|
// "beneficiaryName": "string",
|
||||||
|
// "beneficiaryIban": "string",
|
||||||
|
// "beneficiaryBicOrSwift": "string",
|
||||||
|
// "userThemesTags": [
|
||||||
|
// {
|
||||||
|
// "userId": 0,
|
||||||
|
// "themesTagId": 0,
|
||||||
|
// "themesTag": {
|
||||||
|
// "id": 0,
|
||||||
|
// "groupId": 0,
|
||||||
|
// "name": "string",
|
||||||
|
// "isActive": true,
|
||||||
|
// "userId": 0,
|
||||||
|
// "group": {
|
||||||
|
// "id": 0,
|
||||||
|
// "name": "string",
|
||||||
|
// "isActive": true,
|
||||||
|
// "userId": 0,
|
||||||
|
// "tags": [
|
||||||
|
// "string"
|
||||||
|
// ],
|
||||||
|
// "multilangs": [
|
||||||
|
// {
|
||||||
|
// "parentId": 0,
|
||||||
|
// "languageCode": "string",
|
||||||
|
// "name": "string"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// "userThemesTags": [
|
||||||
|
// "string"
|
||||||
|
// ],
|
||||||
|
// "multilangs": [
|
||||||
|
// {
|
||||||
|
// "parentId": 0,
|
||||||
|
// "languageCode": "string",
|
||||||
|
// "name": "string"
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// },
|
||||||
|
// "user": "string"
|
||||||
|
// }
|
||||||
|
// ],
|
||||||
|
// "languagesLinks": [
|
||||||
|
// {
|
||||||
|
// "userId": 0,
|
||||||
|
// "languageId": 0,
|
||||||
|
// "user": "string",
|
||||||
|
// "language": Language
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
|
// };
|
||||||
|
|
||||||
type SessionTag = {
|
type SessionTag = {
|
||||||
id: number;
|
id: number;
|
||||||
groupId?: number;
|
groupId?: number;
|
||||||
|
@ -21,6 +105,15 @@ export type SessionsFilter = {
|
||||||
endDate?: string;
|
endDate?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type SessionComment = {
|
||||||
|
id: number;
|
||||||
|
createdAtUtc: string;
|
||||||
|
comment?: string;
|
||||||
|
author?: PublicUser;
|
||||||
|
authorId?: number;
|
||||||
|
sessionId?: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type Session = {
|
export type Session = {
|
||||||
id: number;
|
id: number;
|
||||||
scheduledStartAtUtc?: string;
|
scheduledStartAtUtc?: string;
|
||||||
|
@ -37,11 +130,13 @@ export type Session = {
|
||||||
description?: string;
|
description?: string;
|
||||||
isNeedSupervisor?: boolean;
|
isNeedSupervisor?: boolean;
|
||||||
supervisorComment?: string;
|
supervisorComment?: string;
|
||||||
user?: User;
|
user?: PublicUser;
|
||||||
coach?: User;
|
coach?: PublicUser;
|
||||||
supervisor?: User;
|
supervisor?: PublicUser;
|
||||||
clients?: User[];
|
clients?: PublicUser[];
|
||||||
themesTags?: SessionTag[]
|
themesTags?: SessionTag[];
|
||||||
|
coachComments?: SessionComment[];
|
||||||
|
clientComments?: SessionComment[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export enum SessionType {
|
export enum SessionType {
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
export const getDuration = (locale: string, value?: any): string => `${value || 0}${locale === 'ru' ? 'мин' : 'min'}`;
|
||||||
|
|
||||||
|
export const getPrice = (value?: any): string => `${value || 0}€`;
|
Loading…
Reference in New Issue