From ff74e5ba49db5916e8aba83a012e94447e15ed75 Mon Sep 17 00:00:00 2001 From: SD Date: Fri, 9 Aug 2024 12:54:33 +0400 Subject: [PATCH] feat: add experts profile --- messages/de.json | 2 +- messages/es.json | 2 +- messages/fr.json | 2 +- messages/it.json | 2 +- src/actions/helpers.ts | 39 +-- src/actions/hooks/useProfileSettings.ts | 17 +- src/actions/profile.ts | 94 +++++++- src/actions/upload.ts | 13 + .../add-offer/page.tsx | 4 +- .../new-topic/page.tsx | 2 +- .../account/(account)/expert-profile/page.tsx | 66 +++++ .../account/(account)/settings/page.tsx | 8 - .../(account)/work-with-us/coaching/page.tsx | 133 ---------- .../account/(account)/work-with-us/page.tsx | 25 -- src/app/loading.tsx | 2 +- src/components/Account/ProfileSettings.tsx | 227 +++++++++++------- .../sessions/SessionDetailsContent.tsx | 2 +- .../ExpertProfile/EmptyExpertProfile.tsx | 19 ++ .../ExpertProfile/ExpertProfile.tsx | 98 ++++++++ src/components/ExpertProfile/MyOffers.tsx | 34 +++ .../ExpertProfile/content/ExpertEducation.tsx | 65 +++++ .../ExpertProfile/content/ExpertPayData.tsx | 28 +++ .../ExpertProfile/content/ExpertSchedule.tsx | 28 +++ .../ExpertProfile/content/ExpertTags.tsx | 47 ++++ src/components/ExpertProfile/index.ts | 5 + src/components/Experts/ExpertDetails.tsx | 2 +- src/components/Modals/EditExpertTagsModal.tsx | 114 +++++++++ src/i18nKeys/de.ts | 16 +- src/i18nKeys/en.ts | 15 +- src/i18nKeys/es.ts | 20 +- src/i18nKeys/fr.ts | 18 +- src/i18nKeys/it.ts | 20 +- src/i18nKeys/ru.ts | 20 +- src/lib/apiClient.ts | 5 +- src/styles/_default.scss | 15 ++ src/styles/_modal.scss | 49 ++++ src/styles/_pages.scss | 67 +++++- src/types/education.ts | 44 ++++ src/types/experts.ts | 46 +--- src/types/file.ts | 12 + src/types/practice.ts | 29 +++ src/types/profile.ts | 40 ++- src/types/schedule.ts | 10 + src/types/tags.ts | 15 ++ src/utils/account.ts | 18 +- 45 files changed, 1175 insertions(+), 364 deletions(-) create mode 100644 src/actions/upload.ts rename src/app/[locale]/account/(account)/{work-with-us/coaching => expert-profile}/add-offer/page.tsx (97%) rename src/app/[locale]/account/(account)/{work-with-us => expert-profile}/new-topic/page.tsx (97%) create mode 100644 src/app/[locale]/account/(account)/expert-profile/page.tsx delete mode 100644 src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx delete mode 100644 src/app/[locale]/account/(account)/work-with-us/page.tsx create mode 100644 src/components/ExpertProfile/EmptyExpertProfile.tsx create mode 100644 src/components/ExpertProfile/ExpertProfile.tsx create mode 100644 src/components/ExpertProfile/MyOffers.tsx create mode 100644 src/components/ExpertProfile/content/ExpertEducation.tsx create mode 100644 src/components/ExpertProfile/content/ExpertPayData.tsx create mode 100644 src/components/ExpertProfile/content/ExpertSchedule.tsx create mode 100644 src/components/ExpertProfile/content/ExpertTags.tsx create mode 100644 src/components/ExpertProfile/index.ts create mode 100644 src/components/Modals/EditExpertTagsModal.tsx create mode 100644 src/types/education.ts create mode 100644 src/types/file.ts create mode 100644 src/types/practice.ts create mode 100644 src/types/schedule.ts diff --git a/messages/de.json b/messages/de.json index 14d749a..27ce487 100644 --- a/messages/de.json +++ b/messages/de.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Einen Experten finden", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/es.json b/messages/es.json index 0342887..3f7470b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Encontrar un experto", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/fr.json b/messages/fr.json index 48b42e3..bad196d 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Trouver un expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/it.json b/messages/it.json index 379adca..0874d7a 100644 --- a/messages/it.json +++ b/messages/it.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Trova un esperto", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/src/actions/helpers.ts b/src/actions/helpers.ts index d3841c5..6522b69 100644 --- a/src/actions/helpers.ts +++ b/src/actions/helpers.ts @@ -2,7 +2,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; import { apiClient } from '../lib/apiClient'; type RequiredConfigParams = Required> & Pick, 'data'>; -export type PageRequestConfig = RequiredConfigParams & { locale?: string, token?: string }; +export type PageRequestConfig = RequiredConfigParams & Partial> & { locale?: string, token?: string }; export const apiRequest = async ( baseParams: PageRequestConfig, @@ -15,29 +15,30 @@ export const apiRequest = async ( headers: { 'X-User-Language': baseParams?.locale || 'en', 'X-Referrer-Channel': 'site', - ...(baseParams?.token ? { Authorization: `Bearer ${baseParams.token}` } : {}) + ...(baseParams?.token ? { Authorization: `Bearer ${baseParams.token}` } : {}), + ...(baseParams.headers || {}) } }; const response: AxiosResponse = await apiClient.request, T>(config as AxiosRequestConfig); return response.data; } catch (err) { - const { - response: { - status: responseCode = null, - statusText = '', - data: { message = '', status: errorKey = '' } = {}, - } = {}, - code: statusCode = '', - } = err as AxiosError; - - throw new Error( - JSON.stringify({ - statusCode, - statusMessage: message || statusText, - responseCode, - errorKey, - }), - ); + // const { + // response: { + // status: responseCode = null, + // statusText = '', + // data: { message = '', status: errorKey = '' } = {}, + // } = {}, + // code: statusCode = '', + // } = err as AxiosError; + // + // throw new Error( + // JSON.stringify({ + // statusCode, + // statusMessage: message || statusText, + // responseCode, + // errorKey, + // }), + // ); } }; diff --git a/src/actions/hooks/useProfileSettings.ts b/src/actions/hooks/useProfileSettings.ts index 58b02e8..bff3878 100644 --- a/src/actions/hooks/useProfileSettings.ts +++ b/src/actions/hooks/useProfileSettings.ts @@ -1,18 +1,17 @@ 'use client' import { useCallback, useEffect, useState } from 'react'; -import { Profile } from '../../types/profile'; -import { getPersonalData } from '../profile'; +import { ProfileData, ProfileRequest } from '../../types/profile'; +import { getPersonalData, setPersonData } from '../profile'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import { AUTH_TOKEN_KEY } from '../../constants/common'; export const useProfileSettings = (locale: string) => { const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); - const [profileSettings, setProfileSettings] = useState(); + const [profileSettings, setProfileSettings] = useState(); const [fetchLoading, setFetchLoading] = useState(false); - const [saveLoading, setSaveLoading] = useState(false); - useEffect(() => { + const fetchProfileSettings = () => { if (jwt) { getPersonalData(locale, jwt) .then((data) => { @@ -25,16 +24,14 @@ export const useProfileSettings = (locale: string) => { setFetchLoading(false); }); } - }, []); + }; - const save = useCallback(() => { - - }, []); + const save = useCallback((data: ProfileRequest) => setPersonData(data, locale, jwt), []); return { fetchLoading, + fetchProfileSettings, save, - saveLoading, profileSettings }; }; diff --git a/src/actions/profile.ts b/src/actions/profile.ts index d2c9d96..5d517ef 100644 --- a/src/actions/profile.ts +++ b/src/actions/profile.ts @@ -1,7 +1,25 @@ -import { Profile } from '../types/profile'; +import { PayInfo, Profile, ProfileRequest, ProfileData } from '../types/profile'; +import { ExpertsTags } from '../types/tags'; +import { EducationData, EducationDTO } from '../types/education'; +import { PracticeData, PracticeDTO } from '../types/practice'; +import { ScheduleDTO } from '../types/schedule'; import { apiRequest } from './helpers'; -export const setPersonData = (data: { login: string, password: string, role: string, languagesLinks: any[] }, locale: string, token: string): Promise<{ userData: Profile }> => apiRequest({ +export const getUserData = (locale: string, token: string): Promise => apiRequest({ + url: '/home/userdata', + method: 'post', + locale, + token +}); + +export const getPersonalData = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person1', + method: 'post', + locale, + token +}); + +export const setPersonData = (data: ProfileRequest, locale: string, token: string): Promise<{ userData: Profile }> => apiRequest({ url: '/home/applyperson1', method: 'post', data, @@ -9,9 +27,77 @@ export const setPersonData = (data: { login: string, password: string, role: str token }); -export const getPersonalData = (locale: string, token: string): Promise => apiRequest({ - url: '/home/userdata', +export const getEducation = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person2', method: 'post', locale, token }); + +export const setEducation = (locale: string, token: string, data: EducationData): Promise => apiRequest({ + url: '/home/applyperson2', + method: 'post', + data, + locale, + token +}); + +export const getTags = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person3', + method: 'post', + locale, + token +}); + +export const setTags = (locale: string, token: string, data: ExpertsTags): Promise => apiRequest({ + url: '/home/applyperson3', + method: 'post', + data, + locale, + token +}); + +export const getPractice = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person4', + method: 'post', + locale, + token +}); + +export const setPractice = (locale: string, token: string, data: PracticeData): Promise => apiRequest({ + url: '/home/applyperson4', + method: 'post', + data, + locale, + token +}); + +export const getSchedule = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person51', + method: 'post', + locale, + token +}); + +export const setSchedule = (locale: string, token: string, data: ScheduleDTO): Promise => apiRequest({ + url: '/home/applyperson51', + method: 'post', + data, + locale, + token +}); + +export const getPayData = (locale: string, token: string): Promise<{ person6Data?: PayInfo }> => apiRequest({ + url: '/home/person6', + method: 'post', + locale, + token +}); + +export const setPayData = (locale: string, token: string, data: PayInfo): Promise => apiRequest({ + url: '/home/applyperson6', + method: 'post', + data, + locale, + token +}); diff --git a/src/actions/upload.ts b/src/actions/upload.ts new file mode 100644 index 0000000..f41ef95 --- /dev/null +++ b/src/actions/upload.ts @@ -0,0 +1,13 @@ +import { ExpertDocument } from '../types/file'; +import { apiRequest } from './helpers'; + +export const setUploadFile = (locale: string, token: string, data: any): Promise => apiRequest({ + url: '/home/uploadfile', + method: 'post', + data, + locale, + token, + headers: { + 'Content-Type': 'multipart/form-data' + } +}); diff --git a/src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx similarity index 97% rename from src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx rename to src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx index 04ec673..e669b63 100644 --- a/src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx @@ -7,12 +7,12 @@ export default function AddOffer() { <>
  1. - + Work With Us
  2. - + Coaching
  3. diff --git a/src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx b/src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx similarity index 97% rename from src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx rename to src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx index a7b33ed..f8b6774 100644 --- a/src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx @@ -7,7 +7,7 @@ export default function NewTopic() { <>
    1. - + Work With Us
    2. diff --git a/src/app/[locale]/account/(account)/expert-profile/page.tsx b/src/app/[locale]/account/(account)/expert-profile/page.tsx new file mode 100644 index 0000000..c9cd00d --- /dev/null +++ b/src/app/[locale]/account/(account)/expert-profile/page.tsx @@ -0,0 +1,66 @@ +'use client' + +import { useEffect, useState } from 'react'; +import { message } from 'antd'; +// import { unstable_setRequestLocale } from 'next-intl/server'; +import { ExpertData } from '../../../../../types/profile'; +import { AUTH_TOKEN_KEY } from '../../../../../constants/common'; +import { useLocalStorage } from '../../../../../hooks/useLocalStorage'; +import { getEducation, getPersonalData, getTags, getPractice, getSchedule, getPayData } from '../../../../../actions/profile'; +import { ExpertProfile } from '../../../../../components/ExpertProfile'; +import { Loader } from '../../../../../components/view/Loader'; + +export default function ExpertProfilePage({ params: { locale } }: { params: { locale: string } }) { + // unstable_setRequestLocale(locale); + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(); + + useEffect(() => { + if (jwt) { + setLoading(true); + Promise.all([ + getPersonalData(locale, jwt), + getEducation(locale, jwt), + getTags(locale, jwt), + getPractice(locale, jwt), + getSchedule(locale, jwt), + getPayData(locale, jwt) + ]) + .then(([person, education, tags, practice, schedule, payData]) => { + console.log('person', person); + console.log('education', education); + console.log('tags', tags); + console.log('practice', practice); + console.log('schedule', schedule); + console.log('payData', payData); + setData({ + person, + education, + tags, + practice, + schedule, + payData + }); + }) + .catch(() => { + message.error('Не удалось загрузить данные эксперта'); + }) + .finally(() => { + setLoading(false); + }) + } + }, [jwt]); + + return ( + + {data && ( + + )} + + ); +}; diff --git a/src/app/[locale]/account/(account)/settings/page.tsx b/src/app/[locale]/account/(account)/settings/page.tsx index 87504f3..984d596 100644 --- a/src/app/[locale]/account/(account)/settings/page.tsx +++ b/src/app/[locale]/account/(account)/settings/page.tsx @@ -1,18 +1,10 @@ import React, { Suspense } from 'react'; -import type { Metadata } from 'next'; import { unstable_setRequestLocale } from 'next-intl/server'; -import { useTranslations } from 'next-intl'; import { ProfileSettings } from '../../../../../components/Account'; import { i18nText } from '../../../../../i18nKeys'; -export const metadata: Metadata = { - title: 'Bbuddy - Account - Profile Settings', - description: 'Bbuddy desc Profile settings' -}; - export default function Settings({ params: { locale } }: { params: { locale: string } }) { unstable_setRequestLocale(locale); - const t = useTranslations('Account.Settings'); return ( <> diff --git a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx b/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx deleted file mode 100644 index 2b00727..0000000 --- a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import { Link } from '../../../../../../navigation'; -import { i18nText } from '../../../../../../i18nKeys'; - -export default function Coaching({ params: { locale } }: { params: { locale: string } }) { - unstable_setRequestLocale(locale); - - return ( - <> -
        -
      1. - - Work With Us - -
      2. -
      3. Coaching
      4. -
      -
      -
      -
      -
      - -
      -
      -
      - David -
      -
      - 12 Practice hours -
      -
      - 15 Supervision per year -
      -
      -
      -
      -
      - Edit - Add Offer -
      -
      -
      -

      - My Offers -

      -
      -
      -
      -
      - Senior Software Engineer -
      -
      - Edit - Remove -
      -
      -
      - 45$ / 45min -
      -
      -
      Engineering & Data
      -
      Engineering & Data
      -
      +6
      -
      -
      - I have worked across a variety of organizations, lead teams, and delivered quality software - for 8 years. In that time I've worked as an independent consultant, at agencies as a team - lead, and as a senior engineer at Auth0. I also host a podcast - https://anchor.fm/work-in-programming where I break down how … -
      -
      -
      -
      -
      -

      - About Coach -

      -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
      -
      - -
      -

      - Education -

      -
      -

      Psychologist

      -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
      -
      - -
      -
      -
      -
      -

      {i18nText('profCertification', locale)}

      -
      -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
      -
      -
      -
      -

      - Trainings | Seminars | Courses -

      -
      -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
      -
      -
      -
      -

      - MBA Information -

      -
      -
      - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
      -
      -
      - - ); -} diff --git a/src/app/[locale]/account/(account)/work-with-us/page.tsx b/src/app/[locale]/account/(account)/work-with-us/page.tsx deleted file mode 100644 index 465b5a0..0000000 --- a/src/app/[locale]/account/(account)/work-with-us/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import { i18nText } from '../../../../../i18nKeys'; - -export default function WorkWithUs({ params: { locale } }: { params: { locale: string } }) { - unstable_setRequestLocale(locale); - - return ( - <> -
        -
      1. {i18nText('accountMenu.work-with-us', locale)}
      2. -
      -
      -
      - -
      -
      -
      {i18nText('insertInfo', locale)}
      -
      {i18nText('changeUserData', locale)}
      - -
      -
      - - ); -} diff --git a/src/app/loading.tsx b/src/app/loading.tsx index b64006d..937170a 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -6,4 +6,4 @@ export default function Loading() { ...loading ); -} +}; diff --git a/src/components/Account/ProfileSettings.tsx b/src/components/Account/ProfileSettings.tsx index 0d6c4a4..674ae2c 100644 --- a/src/components/Account/ProfileSettings.tsx +++ b/src/components/Account/ProfileSettings.tsx @@ -1,125 +1,190 @@ 'use client'; import React, { FC, useEffect, useState } from 'react'; -import { Form, Upload } from 'antd'; -import type { UploadFile, UploadProps } from 'antd'; +import { Button, Form, message, Upload } from 'antd'; +import type { GetProp, UploadFile, UploadProps } from 'antd'; import ImgCrop from 'antd-img-crop'; import { CameraOutlined, DeleteOutlined } from '@ant-design/icons'; import { useRouter } from '../../navigation'; import { i18nText } from '../../i18nKeys'; -import { Profile } from '../../types/profile'; +import { ProfileRequest } from '../../types/profile'; +import { validateImage } from '../../utils/account'; import { useProfileSettings } from '../../actions/hooks/useProfileSettings'; import { CustomInput } from '../view/CustomInput'; import { OutlinedButton } from '../view/OutlinedButton'; import { FilledYellowButton } from '../view/FilledButton'; -import { DeleteAccountModal } from "../Modals/DeleteAccountModal"; +import { DeleteAccountModal } from '../Modals/DeleteAccountModal'; +import { Loader } from '../view/Loader'; type ProfileSettingsProps = { locale: string; }; -// type FileType = Parameters>[0]; +type FileType = Parameters>[0]; export const ProfileSettings: FC = ({ locale }) => { - const [form] = Form.useForm(); - const { profileSettings } = useProfileSettings(locale); + const [form] = Form.useForm(); + const { profileSettings, fetchProfileSettings, save, fetchLoading } = useProfileSettings(locale); const [showDeleteModal, setShowDeleteModal] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); + const [photo, setPhoto] = useState(); const router = useRouter(); + useEffect(() => { + fetchProfileSettings() + }, []); + useEffect(() => { if (profileSettings) { form.setFieldsValue(profileSettings); } }, [profileSettings]); - const saveProfileSettings = () => { + const onSaveProfile = () => { form.validateFields() - .then(() => { - console.log('success') + .then(({ login, surname, username }) => { + const { phone, role, languagesLinks } = profileSettings; + const newProfile: ProfileRequest = { + phone, + role, + login, + surname, + username, + isPasswordKeepExisting: true, + isFaceImageKeepExisting: true, + languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || [] + }; + + // if (photo) { + // console.log(photo); + // const formData = new FormData(); + // formData.append('file', photo as FileType); + // + // newProfile.faceImage = photo; + // newProfile.isFaceImageKeepExisting = false; + // } + + console.log(newProfile); + + setSaveLoading(true); + save(newProfile) + .then(() => { + fetchProfileSettings(); + }) + .catch(() => { + message.error('Не удалось сохранить изменения'); + }) + .finally(() => { + setSaveLoading(false); + }) }) } - const [fileList, setFileList] = useState(); + const beforeCrop = (file: UploadFile) => { + return validateImage(file, true); + } - const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { - setFileList(newFileList); - }; + const beforeUpload = (file: UploadFile) => { + const isValid = validateImage(file); - const onPreview = async (file: UploadFile) => { - // let src = file.url as string; - // if (!src) { - // src = await new Promise((resolve) => { - // const reader = new FileReader(); - // reader.readAsDataURL(file.originFileObj as FileType); - // reader.onload = () => resolve(reader.result as string); - // }); - // } - // const image = new Image(); - // image.src = src; - // const imgWindow = window.open(src); - // imgWindow?.document.write(image.outerHTML); - }; + if (isValid) { + setPhoto(file); + } + + return false; + } const onDeleteAccount = () => setShowDeleteModal(true); return ( -
      -
      -
      - -
      - {/* - - - - */} -
      -
      - - + + {photo && } + + + +
      +
      + + + +
      +
      + + + +
      + {/*
      + + +
      */} +
      + + + +
      -
      - - - +
      + + {i18nText('save', locale)} + + router.push('change-password')}> + {i18nText('changePass', locale)} + + } + danger + > + {i18nText('deleteAcc', locale)} +
      - {/*
      - - - -
      */} -
      - - - -
      -
      -
      - {i18nText('save', locale)} - router.push('change-password')}> - {i18nText('changePass', locale)} - - } - danger - > - {i18nText('deleteAcc', locale)} - -
      - setShowDeleteModal(false)} - /> - + setShowDeleteModal(false)} + /> + + ); }; diff --git a/src/components/Account/sessions/SessionDetailsContent.tsx b/src/components/Account/sessions/SessionDetailsContent.tsx index f714917..f32622e 100644 --- a/src/components/Account/sessions/SessionDetailsContent.tsx +++ b/src/components/Account/sessions/SessionDetailsContent.tsx @@ -283,7 +283,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio ))} {(isCoach ? session?.clientComments : session?.coachComments)?.length > 0 && (
      - {isCoach ? 'Client Comments' : 'Coach Comments'} + {isCoach ? i18nText('session.clientComments', locale) : i18nText('session.coachComments', locale)}
      )} {(isCoach ? session?.clientComments : session?.coachComments)?.map(({ id , comment }) => ( diff --git a/src/components/ExpertProfile/EmptyExpertProfile.tsx b/src/components/ExpertProfile/EmptyExpertProfile.tsx new file mode 100644 index 0000000..aaa7fcf --- /dev/null +++ b/src/components/ExpertProfile/EmptyExpertProfile.tsx @@ -0,0 +1,19 @@ +import { i18nText } from '../../i18nKeys'; + +export const EmptyExpertProfile = ({ locale }: { locale: string }) => ( + <> +
        +
      1. {i18nText('accountMenu.expert-profile', locale)}
      2. +
      +
      +
      + +
      +
      +
      {i18nText('insertInfo', locale)}
      +
      {i18nText('changeUserData', locale)}
      + +
      +
      + +); diff --git a/src/components/ExpertProfile/ExpertProfile.tsx b/src/components/ExpertProfile/ExpertProfile.tsx new file mode 100644 index 0000000..669bf77 --- /dev/null +++ b/src/components/ExpertProfile/ExpertProfile.tsx @@ -0,0 +1,98 @@ +'use client' + +import { useState } from 'react'; +import { message } from 'antd'; +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { ExpertData } from '../../types/profile'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { getTags } from '../../actions/profile'; +import { Loader } from '../view/Loader'; +import { LinkButton } from '../view/LinkButton'; +import { ExpertTags } from './content/ExpertTags'; +import { ExpertSchedule } from './content/ExpertSchedule'; +import { ExpertPayData } from './content/ExpertPayData'; +import { ExpertEducation } from './content/ExpertEducation'; + +type ExpertProfileProps = { + locale: string; + data: ExpertData; + updateData: (data: ExpertData) => void; +}; + +export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState<(keyof ExpertData)[]>([]); + + const updateExpert = (key: keyof ExpertData) => { + switch (key) { + case 'tags': + setLoading([key]); + getTags(locale, jwt) + .then((tags) => { + updateData({ + ...data, + tags + }); + }) + .catch(() => message.error('Не удалось обновить направления')) + .finally(() => setLoading([])); + break; + default: + break; + } + }; + + return ( + <> +
        +
      1. {i18nText('coaching', locale)}
      2. +
      +
      +
      +
      + +
      +
      +
      + David +
      +
      +
      +
      +
      +
      +

      {i18nText('aboutCoach', locale)}

      +

      person1 + person4

      + } + /> +
      +
      + {`12 ${i18nText('practiceHours', locale)}`} +
      +
      + {`15 ${i18nText('supervisionCount', locale)}`} +
      +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
      +
      +
      + + + + + + +
      + + ) +}; diff --git a/src/components/ExpertProfile/MyOffers.tsx b/src/components/ExpertProfile/MyOffers.tsx new file mode 100644 index 0000000..7a3111c --- /dev/null +++ b/src/components/ExpertProfile/MyOffers.tsx @@ -0,0 +1,34 @@ +export const MyOffers = () => ( +
      +

      + My Offers +

      +
      +
      +
      +
      + Senior Software Engineer +
      +
      + Edit + Remove +
      +
      +
      + 45$ / 45min +
      +
      +
      Engineering & Data
      +
      Engineering & Data
      +
      +6
      +
      +
      + I have worked across a variety of organizations, lead teams, and delivered quality software + for 8 years. In that time I've worked as an independent consultant, at agencies as a team + lead, and as a senior engineer at Auth0. I also host a podcast + https://anchor.fm/work-in-programming where I break down how … +
      +
      +
      +
      +); diff --git a/src/components/ExpertProfile/content/ExpertEducation.tsx b/src/components/ExpertProfile/content/ExpertEducation.tsx new file mode 100644 index 0000000..8ddc1e6 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertEducation.tsx @@ -0,0 +1,65 @@ +import { EditOutlined } from '@ant-design/icons'; +import { EducationDTO } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertEducationProps = { + locale: string; + data?: EducationDTO; +}; + +export const ExpertEducation = ({ locale, data }: ExpertEducationProps) => { + return ( +
      +
      +
      +

      {i18nText('education', locale)}

      +

      person2

      + } + /> +
      +
      +

      Psychologist

      +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
      +
      + +
      +
      +
      +
      +

      {i18nText('profCertification', locale)}

      +
      +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
      +
      +
      +
      +

      + {`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`} +

      +
      +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
      +
      +
      +
      +

      {i18nText('mba', locale)}

      +
      +
      + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
      +
      +
      +
      + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertPayData.tsx b/src/components/ExpertProfile/content/ExpertPayData.tsx new file mode 100644 index 0000000..648aed2 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertPayData.tsx @@ -0,0 +1,28 @@ +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../../i18nKeys'; +import { PayInfo } from '../../../types/profile'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertPayDataProps = { + locale: string; + data?: PayInfo +}; + +export const ExpertPayData = ({ locale, data }: ExpertPayDataProps) => { + return ( +
      +
      +
      +

      Card data - person6

      + } + /> +
      +
      + Card +
      +
      +
      + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertSchedule.tsx b/src/components/ExpertProfile/content/ExpertSchedule.tsx new file mode 100644 index 0000000..907aa22 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertSchedule.tsx @@ -0,0 +1,28 @@ +import { EditOutlined } from '@ant-design/icons'; +import { ScheduleDTO } from '../../../types/schedule'; +import { i18nText } from '../../../i18nKeys'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertScheduleProps = { + locale: string; + data?: ScheduleDTO; +}; + +export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => { + return ( +
      +
      +
      +

      Schedule - person51

      + } + /> +
      +
      + Schedule +
      +
      +
      + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertTags.tsx b/src/components/ExpertProfile/content/ExpertTags.tsx new file mode 100644 index 0000000..7441bed --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertTags.tsx @@ -0,0 +1,47 @@ +'use client' + +import { useState } from 'react'; +import { Tag } from 'antd'; +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../../i18nKeys'; +import { ExpertsTags } from '../../../types/tags'; +import { ExpertData } from '../../../types/profile'; +import { LinkButton } from '../../view/LinkButton'; +import { EditExpertTagsModal } from '../../Modals/EditExpertTagsModal'; + +type ExpertTagsProps = { + locale: string; + data?: ExpertsTags; + updateExpert: (key: keyof ExpertData) => void; +} + +export const ExpertTags = ({ locale, data, updateExpert }: ExpertTagsProps) => { + const [showEdit, setShowEdit] = useState(false); + + return ( +
      +
      +
      +

      {i18nText('direction', locale)}

      + } + onClick={() => setShowEdit(true)} + /> +
      +
      + {data?.themesTags && data.themesTags?.length > 0 && data.themesTags + .filter(({ isActive, isSelected }) => isActive && isSelected) + .map(({ id, name }) => {name})} +
      +
      + setShowEdit(false)} + refresh={() => updateExpert('tags')} + /> +
      + ); +}; diff --git a/src/components/ExpertProfile/index.ts b/src/components/ExpertProfile/index.ts new file mode 100644 index 0000000..0fc5003 --- /dev/null +++ b/src/components/ExpertProfile/index.ts @@ -0,0 +1,5 @@ +'use client' + +export * from './EmptyExpertProfile'; +export * from './ExpertProfile'; +export * from './MyOffers'; diff --git a/src/components/Experts/ExpertDetails.tsx b/src/components/Experts/ExpertDetails.tsx index 4398016..e2c942c 100644 --- a/src/components/Experts/ExpertDetails.tsx +++ b/src/components/Experts/ExpertDetails.tsx @@ -98,7 +98,7 @@ export const ExpertPractice: FC = ({ expert, locale }) => { return practiceCases?.length > 0 ? (
      -

      Successful Cases From Practice

      +

      {i18nText('successfulCase', locale)}

      {practiceCases?.map(({ id, description, themesGroupIds }) => { const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id)); diff --git a/src/components/Modals/EditExpertTagsModal.tsx b/src/components/Modals/EditExpertTagsModal.tsx new file mode 100644 index 0000000..9b1677a --- /dev/null +++ b/src/components/Modals/EditExpertTagsModal.tsx @@ -0,0 +1,114 @@ +'use client'; + +import React, {FC, useCallback, useEffect, useState} from 'react'; +import { Modal, Button, List, message } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { ExpertsTags, Tag } from '../../types/tags'; +import { CustomSwitch } from '../view/CustomSwitch'; +import { setTags } from '../../actions/profile'; + +type EditExpertTagsModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: ExpertsTags; + refresh: () => void; +}; + +export const EditExpertTagsModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh +}) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [expertTags, setExpertTags] = useState([]); + + useEffect(() => { + setExpertTags(data?.themesTags || []); + }, [data]); + + const onSaveTags = () => { + setLoading(true); + setTags(locale, jwt, { themesGroups: data?.themesGroups, themesTags: expertTags }) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить направления'); + }) + .finally(() => { + setLoading(false); + }) + }; + + const updateTag = useCallback((id: number, isSelected: boolean) => { + setExpertTags(expertTags.map((tag) => { + if (tag.id === id) { + tag.isSelected = isSelected; + } + return tag; + })) + }, [expertTags, setExpertTags]); + + return ( + } + > +
      +
      {i18nText('direction', locale)}
      +
      + {data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => ( +
      +

      {name}

      + {expertTags?.length > 0 ? ( +
      + (isActive && groupId == id)) || []} + split={false} + style={{ width: '100%' }} + renderItem={({ id, name, isSelected }) => ( + +
      +
      {name}
      + updateTag(id, checked)} + /> +
      +
      + )} + /> +
      + ) :
      No tags
      } +
      + ))} +
      +
      + +
      +
      +
      + ); +}; diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index 21bb176..f27812b 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -4,9 +4,9 @@ export default { notifications: 'Benachrichtigung', support: 'Hilfe & Support', information: 'Rechtliche Informationen', - settings: 'Profileinstellungen', + settings: 'Kontoeinstellungen', messages: 'Nachrichten', - 'work-with-us': 'Arbeite mit uns' + 'expert-profile': 'Expertenprofil' }, menu: { 'bb-client': 'Mit BB wachsen', @@ -41,6 +41,8 @@ export default { myComments: 'Meine Kommentare', addComment: 'Neuen Kommentar hinzufügen', commentPlaceholder: 'Ihr Kommentar', + clientComments: 'Kundenkommentare', + coachComments: 'Trainerkommentare' }, room: { upcoming: 'Zukünftige Räume', @@ -84,6 +86,7 @@ export default { fromTo: 'von $ bis $', apply: 'Anwenden', save: 'Speichern', + edit: 'Bearbeiten', changePass: 'Passwort ändern', resetPass: 'Passwort zurücksetzen', getStarted: 'Loslegen', @@ -98,9 +101,18 @@ export default { supervisionCount: 'Supervision pro Jahr', outOf: 'von', schedule: 'Zeitplan', + successfulCase: 'Erfolgreiche Fälle aus der Praxis', signUp: 'Jetzt anmelden', noData: 'Keine Daten', notFound: 'Nicht gefunden', + trainings: 'Trainings', + seminars: 'Seminare', + courses: 'Kurse', + mba: 'MBA-Information', + aboutCoach: 'Über Coach', + education: 'Bildung', + coaching: 'Coaching', + errors: { invalidEmail: 'Die E-Mail-Adresse ist ungültig', emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index cc538c3..9857844 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notification', support: 'Help & Support', information: 'Legal Information', - settings: 'Profile Settings', + settings: 'Account Settings', messages: 'Messages', - 'work-with-us': 'Work With Us' + 'expert-profile': 'Expert profile' }, menu: { 'bb-client': 'Start grow with BB', @@ -41,6 +41,8 @@ export default { myComments: 'My comments', addComment: 'Add new', commentPlaceholder: 'Your comment', + clientComments: 'Client Comments', + coachComments: 'Coach Comments' }, room: { upcoming: 'Upcoming Rooms', @@ -84,6 +86,7 @@ export default { fromTo: 'from $ to $', apply: 'Apply', save: 'Save', + edit: 'Edit', changePass: 'Change password', resetPass: 'Reset password', getStarted: 'Get started', @@ -98,9 +101,17 @@ export default { supervisionCount: 'Supervision per year', outOf: 'out of', schedule: 'Schedule', + successfulCase: 'Successful Cases From Practice', signUp: 'Sign up now', noData: 'No data', notFound: 'Not found', + trainings: 'Trainings', + seminars: 'Seminars', + courses: 'Courses', + mba: 'MBA Information', + aboutCoach: 'About Coach', + education: 'Education', + coaching: 'Coaching', errors: { invalidEmail: 'The email address is not valid', emptyEmail: 'Please enter your E-mail', diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index 9c7d096..f94d8a5 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notificación', support: 'Ayuda y asistencia', information: 'Información jurídica', - settings: 'Ajustes del perfil', + settings: 'Ajustes de cuenta', messages: 'Mensajes', - 'work-with-us': 'Trabaja con nosotros' + 'expert-profile': 'Perfil del experto' }, menu: { 'bb-client': 'Empieza a crecer con BB', @@ -41,6 +41,8 @@ export default { myComments: 'Mis comentarios', addComment: 'Añadir nuevo comentario', commentPlaceholder: 'Tu comentario', + clientComments: 'Comentarios del cliente', + coachComments: 'Comentarios del entrenador' }, room: { upcoming: 'Próximas salas', @@ -84,6 +86,7 @@ export default { fromTo: 'de $ a $', apply: 'Solicitar', save: 'Guardar', + edit: 'Editar', changePass: 'Cambiar contraseña', resetPass: 'Restablecer contraseña', getStarted: 'Empieza', @@ -94,13 +97,22 @@ export default { courseInfo: 'Información del curso', expertBackground: 'Antecedentes del experto', profCertification: 'Certificación profesional', - practiceHours: 'horas de práctica', - supervisionCount: 'supervisiones anuales', + practiceHours: 'Horas de práctica', + supervisionCount: 'Supervisiones anuales', outOf: 'de', schedule: 'Horario', + successfulCase: 'Casos de éxito de la práctica', signUp: 'Regístrate ahora', noData: 'Sin datos', notFound: 'No encontrado', + trainings: 'Formación', + seminars: 'Seminarios', + courses: 'Cursos', + mba: 'Información sobre máster en ADE (MBA)', + aboutCoach: 'Sobre el coach', + education: 'Educación', + coaching: 'Coaching', + errors: { invalidEmail: 'La dirección de correo electrónico no es válida', emptyEmail: 'Introduce tu correo electrónico', diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index 9b5c471..7978155 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notification', support: 'Aide et support', information: 'Informations légales', - settings: 'Paramètres du profil', + settings: 'Paramètres du compte', messages: 'Messages', - 'work-with-us': 'Travaillez avec nous' + 'expert-profile': 'Profil de l\'expert' }, menu: { 'bb-client': 'Commencez à vous développer avec BB', @@ -41,6 +41,8 @@ export default { myComments: 'Mes commentaires', addComment: 'Ajouter un nouveau commentaire', commentPlaceholder: 'Votre commentaire', + clientComments: 'Commentaires du client', + coachComments: 'Commentaires du coach' }, room: { upcoming: 'Salles futures', @@ -84,6 +86,7 @@ export default { fromTo: 'de $ à $', apply: 'Appliquer', save: 'Sauvegarder', + edit: 'Modifier', changePass: 'Modifier le mot de passe', resetPass: 'Réinitialiser le mot de passe', getStarted: 'Commencer', @@ -94,13 +97,22 @@ export default { courseInfo: 'Infos sur le cours', expertBackground: 'Antécédents de l\'expert', profCertification: 'Certification professionnelle', - practiceHours: 'heures de pratique', + practiceHours: 'Heures de pratique', supervisionCount: 'Supervision par an', outOf: 'sur', schedule: 'Programme', + successfulCase: 'Cas réussis de la pratique', signUp: 'Inscrivez-vous maintenant', noData: 'Aucune donnée', notFound: 'Non trouvé', + trainings: 'Formations', + seminars: 'Séminaires', + courses: 'Cours', + mba: 'Infos Maîtrise en gestion', + aboutCoach: 'À propos du coach', + education: 'Éducation', + coaching: 'Coaching', + errors: { invalidEmail: 'L\'adresse e-mail n\'est pas valide', emptyEmail: 'Veuillez saisir votre e-mail', diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 575b165..5520c2a 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notifica', support: 'Assistenza e supporto', information: 'Informazioni legali', - settings: 'Impostazioni profilo', + settings: 'Impostazioni account', messages: 'Messaggi', - 'work-with-us': 'Lavora con noi' + 'expert-profile': 'Profilo dell\'esperto' }, menu: { 'bb-client': 'Inizia a crescere con BB', @@ -41,6 +41,8 @@ export default { myComments: 'I miei commenti', addComment: 'Aggiungi nuovo commento', commentPlaceholder: 'Il tuo commento', + clientComments: 'Commenti del cliente', + coachComments: 'Commenti dell\'allenatore' }, room: { upcoming: 'Prossime sale', @@ -84,6 +86,7 @@ export default { fromTo: 'da $ a $', apply: 'Applica', save: 'Salva', + edit: 'Modifica', changePass: 'Cambia password', resetPass: 'Reimposta password', getStarted: 'Inizia', @@ -94,13 +97,22 @@ export default { courseInfo: 'Informazioni sul corso', expertBackground: 'Background esperto', profCertification: 'Certificazione professionale', - practiceHours: 'ore di pratica', - supervisionCount: 'supervisioni per anno', + practiceHours: 'Ore di pratica', + supervisionCount: 'Supervisioni per anno', outOf: 'su', schedule: 'Programma', + successfulCase: 'Casi di successo dalla pratica', signUp: 'Iscriviti ora', noData: 'Nessun dato', notFound: 'Non trovato', + trainings: 'Training', + seminars: 'Seminari', + courses: 'Corsi', + mba: 'Info sull\'MBA', + aboutCoach: 'Informazioni sul coach', + education: 'Istruzione', + coaching: 'Coaching', + errors: { invalidEmail: 'L\'indirizzo e-mail non è valido', emptyEmail: 'Inserisci l\'e-mail', diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index 16da8a3..aa99952 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -4,9 +4,9 @@ export default { notifications: 'Уведомления', support: 'Служба поддержки', information: 'Юридическая информация', - settings: 'Настройки профиля', + settings: 'Настройки учетной записи', messages: 'Сообщения', - 'work-with-us': 'Сотрудничество' + 'expert-profile': 'Профиль эксперта' }, menu: { 'bb-client': 'Начните свой рост с BB', @@ -41,6 +41,8 @@ export default { myComments: 'Мои комментарии', addComment: 'Добавить новый', commentPlaceholder: 'Ваш комментарий', + clientComments: 'Комментарии клиента', + coachComments: 'Комментарии коуча' }, room: { upcoming: 'Предстоящие комнаты', @@ -84,6 +86,7 @@ export default { fromTo: 'от $ до $', apply: 'Применить', save: 'Сохранить', + edit: 'Редактировать', changePass: 'Изменить пароль', resetPass: 'Сбросить пароль', getStarted: 'Начать работу', @@ -94,13 +97,22 @@ export default { courseInfo: 'Информация о курсе', expertBackground: 'Профессиональный опыт эксперта', profCertification: 'Профессиональная сертификация', - practiceHours: 'часов практики', - supervisionCount: 'часов супервизии в год', + practiceHours: 'Часов практики', + supervisionCount: 'Часов супервизии в год', outOf: 'из', schedule: 'Расписание', + successfulCase: 'Успешные случаи из практики', signUp: 'Записаться сейчас', noData: 'Нет данных', notFound: 'Не найдено', + trainings: 'Тренинги', + seminars: 'Семинары', + courses: 'Курсы', + mba: 'Информация о MBA', + aboutCoach: 'О коуче', + education: 'Образование', + coaching: 'Коучинг', + errors: { invalidEmail: 'Адрес электронной почты недействителен', emptyEmail: 'Пожалуйста, введите ваш E-mail', diff --git a/src/lib/apiClient.ts b/src/lib/apiClient.ts index f6beada..713dbe7 100644 --- a/src/lib/apiClient.ts +++ b/src/lib/apiClient.ts @@ -12,7 +12,10 @@ export const onSuccessRequestCallback = (config: InternalAxiosRequestConfig) => // if (IS_DEV && !newConfig.headers.Authorization && getAuthToken()) { // newConfig.headers.Authorization = `Bearer ${getAuthToken()}`; // } - newConfig.headers['Content-Type'] = 'application/json'; + + if (!newConfig.headers['Content-Type']) { + newConfig.headers['Content-Type'] = 'application/json'; + } return newConfig; }; diff --git a/src/styles/_default.scss b/src/styles/_default.scss index 3f7f347..93f42f9 100644 --- a/src/styles/_default.scss +++ b/src/styles/_default.scss @@ -8,6 +8,21 @@ body{ --font: var(--font-comfortaa), -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-family: var(--font); background-color: #ffffff; + + & * { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + &::-webkit-scrollbar-track { + background: #C4DFE6; + border-radius: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: #2C7873; + border-radius: 8px; + } + } } *::selection { diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index c04824a..bf5423e 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -32,6 +32,55 @@ padding: 44px 40px; gap: 24px; } + + &__expert { + &__title { + color: #003B46; + @include rem(20); + font-style: normal; + font-weight: 600; + line-height: 133.333%; + } + + &__button { + width: 100%; + + button { + width: 100% !important; + } + } + + &__inner { + height: 60vh; + overflow-y: auto; + + & > div { + display: flex; + flex-direction: column; + padding-right: 20px; + + & > * { + width: 100%; + } + } + } + + &__content { + display: flex; + flex-direction: column; + padding: 40px; + gap: 24px; + + .title-h4 { + color: #003B46; + @include rem(16); + font-style: normal; + font-weight: 700; + line-height: 150%; + padding-bottom: 16px; + } + } + } } .ant-modal-mask { diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index 200aea8..49ceb51 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -1140,15 +1140,13 @@ height: 146px; object-fit: cover; } - } .coaching-info { display: flex; flex-flow: column; justify-content: space-between; - gap: 24px; - margin-bottom: 24px; + gap: 16px; .card-profile { border: none !important; @@ -1172,9 +1170,6 @@ } @media (min-width: 768px) { - flex-flow: nowrap; - gap: 10px; - &__wrap-btn { display: flex; gap: 10px; @@ -1183,8 +1178,66 @@ } } +.coaching-profile { + display: flex; + gap: 16px; + align-items: flex-start; + + &__portrait { + width: 86px; + height: 86px; + border-radius: 16px; + border: 2px solid #FFF; + background: lightgray 50%; + box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32); + overflow: hidden; + + img { + object-fit: cover; + width: 100%; + height: 100%; + display: block; + border-radius: 16px; + } + } + + &__inner { + padding-top: 10px; + } + + &__name { + color: #003B46; + @include rem(18); + font-weight: 600; + line-height: 150%; + } +} + .coaching-section { - margin-bottom: 24px; + &__wrap { + border-top: 1px solid #C4DFE6; + padding-top: 16px; + display: flex; + flex-direction: column; + gap: 16px; + } + + &__title { + display: flex; + width: 100%; + justify-content: space-between; + align-self: center; + + .b-button__link { + height: 24px !important; + } + } + + .title-h2 { + color: #003B46; + @include rem(18); + line-height: 24px; + } .base-text { margin-bottom: 0; diff --git a/src/types/education.ts b/src/types/education.ts new file mode 100644 index 0000000..aa41a70 --- /dev/null +++ b/src/types/education.ts @@ -0,0 +1,44 @@ +import { ExpertDocument } from './file'; + +export type Details = { + id: number; + userId?:number; + title?: string; + description?: string; + document?: ExpertDocument; +}; + +export type Certificate = { + id: number; + userId?: number; + associationLevelId?: number; + document?: ExpertDocument; +}; + +export type Experience = { + id: number, + userId?: number, + title?: string, + description?: string +}; + +export type Association = { + id: number; + name?: string; +}; + +export type AssociationLevel = Association & { associationId?: number }; + +export type EducationData = { + certificates?: Certificate[], + educations?: Details[], + trainings?: Details[], + mbas?: Details[], + experiences?: Experience[] +}; + +export interface EducationDTO { + person2Data?: EducationData, + associations?: Association[], + associationLevels?: AssociationLevel[] +} diff --git a/src/types/experts.ts b/src/types/experts.ts index 160db87..5917d31 100644 --- a/src/types/experts.ts +++ b/src/types/experts.ts @@ -1,4 +1,5 @@ import { Tag, ThemeGroups } from './tags'; +import { Association, AssociationLevel, EducationData } from './education'; export type GeneralFilter = Filter & AdditionalFilter; @@ -19,34 +20,6 @@ export type AdditionalFilter = { coachSort?: string; }; -export type File = { - id: number; - fileType: string; - url: string; -}; - -export interface ExpertDocument { - fileName: string; - original?: File; - preview?: File; - fullSize?: File; -} - -export type Details = { - id: number; - userId?:number; - title?: string; - description?: string; - document?: ExpertDocument; -}; - -export type Certificate = { - id: number; - userId?: number; - associationLevelId?: number; - document?: ExpertDocument; -}; - export type Practice = { id: number; userId?: number; @@ -61,16 +34,6 @@ export type ThemeGroup = { canDeleted?: boolean; }; -export type Association = { - id: number; - name: string; -}; - -export type AssociationLevel = { - id: number; - associationId: number; - name: string; -}; export interface ExpertItem { id: number; @@ -98,14 +61,9 @@ export type ExpertsData = { }; export type ExpertDetails = { - publicCoachDetails: ExpertItem & { + publicCoachDetails: ExpertItem & EducationData & { practiceHours?: number; supervisionPerYearId?: number; - educations?: Details[]; - certificates?: Certificate[]; - trainings?: Details[]; - mbas?: Details[]; - experiences?: Details[]; practiceCases?: Practice[]; themesGroups?: ThemeGroup[]; }; diff --git a/src/types/file.ts b/src/types/file.ts new file mode 100644 index 0000000..48a73fd --- /dev/null +++ b/src/types/file.ts @@ -0,0 +1,12 @@ +export type File = { + id: number; + fileType: string; + url: string; +}; + +export interface ExpertDocument { + fileName: string; + original?: File; + preview?: File; + fullSize?: File; +} diff --git a/src/types/practice.ts b/src/types/practice.ts new file mode 100644 index 0000000..438e125 --- /dev/null +++ b/src/types/practice.ts @@ -0,0 +1,29 @@ +import { ExpertsThemesGroups } from './tags'; + +export type Supervision = { + id: number, + name: string +}; + +export type PracticeCase = { + id: number, + userId?: number, + description?: string, + themesGroupIds?: number[] +}; + +export type PracticeData = { + practiceHours?: number, + supervisionPerYearId?: number, + sessionDuration?: number, + sessionCost?: number, + practiceCases?: PracticeCase[] +} + +export interface PracticeDTO { + person4Data: PracticeData & { + themesGroups?: ExpertsThemesGroups[], + supervisionPerYears?: Supervision[], + sessionCosts?: number[] + } +} diff --git a/src/types/profile.ts b/src/types/profile.ts index c606879..548398c 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -1,5 +1,10 @@ -export type Profile = { - id: number; +import { UploadFile } from 'antd'; +import {EducationDTO} from "./education"; +import {ExpertsTags} from "./tags"; +import {PracticeDTO} from "./practice"; +import {ScheduleDTO} from "./schedule"; + +export type ProfileData = { username?: string; surname?: string; fillProgress?: string; @@ -9,4 +14,35 @@ export type Profile = { hasPassword?: boolean; hasExternalLogin?: boolean; isTestMode?: boolean; + phone?: string; + languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[] +} + +export type Profile = ProfileData & { id: number }; + +export type ProfileRequest = { + login?: string; + password?: string; + isPasswordKeepExisting?: boolean; + languagesLinks?: { languageId: number }[]; + username?: string; + surname?: string; + faceImage?: UploadFile; + isFaceImageKeepExisting?: boolean; + phone?: string; }; + +export type PayInfo = { + beneficiaryName?: string, + iban?: string, + bicOrSwift?: string +}; + +export interface ExpertData { + person?: ProfileData, + education?: EducationDTO, + tags?: ExpertsTags, + practice?: PracticeDTO, + schedule?: ScheduleDTO, + payData?: { person6Data?: PayInfo }, +} diff --git a/src/types/schedule.ts b/src/types/schedule.ts new file mode 100644 index 0000000..fc18ca9 --- /dev/null +++ b/src/types/schedule.ts @@ -0,0 +1,10 @@ +export type WorkingTime = { + startDayOfWeekUtc?: string, + startTimeUtc?: number, + endDayOfWeekUtc?: string, + endTimeUtc?: number +} + +export interface ScheduleDTO { + workingTimes?: WorkingTime[] +} diff --git a/src/types/tags.ts b/src/types/tags.ts index 821ebec..eb1985e 100644 --- a/src/types/tags.ts +++ b/src/types/tags.ts @@ -4,6 +4,9 @@ export type Tag = { name: string couchCount?: number; group?: string | null; + isActive?: boolean, + isSelected?: boolean, + canDeleted?: boolean }; export type ThemeGroups = { @@ -27,3 +30,15 @@ export type Language = { } export type Languages = Language[]; + +export type ExpertsThemesGroups = { + id: number, + name: string, + isActive?: boolean, + canDeleted?: boolean +}; + +export interface ExpertsTags { + themesGroups?: ExpertsThemesGroups[], + themesTags?: Tag[] +} diff --git a/src/utils/account.ts b/src/utils/account.ts index 91725b1..e67e439 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -1,6 +1,8 @@ +import { message } from 'antd'; +import type { UploadFile } from 'antd'; import { i18nText } from '../i18nKeys'; -const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'work-with-us']; +const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile']; const COUNTS: Record = { sessions: 12, notifications: 5, @@ -12,3 +14,17 @@ export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({ title: i18nText(`accountMenu.${path}`, locale), count: COUNTS[path] || undefined })); + +export const validateImage = (file: UploadFile, showMessage?: boolean): boolean => { + const isImage = file.type === 'image/jpg' || file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif'; + if (!isImage && showMessage) { + message.error('You can only upload JPG/PNG file'); + } + + const isLt5M = file.size / 1024 / 1024 <= 5; + if (!isLt5M && showMessage) { + message.error('Image must smaller than 5MB'); + } + + return isImage && isLt5M; +};