feat: add experts profile
This commit is contained in:
parent
abf04b4c5b
commit
ff74e5ba49
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -2,7 +2,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios';
|
|||
import { apiClient } from '../lib/apiClient';
|
||||
|
||||
type RequiredConfigParams<D = any> = Required<Pick<AxiosRequestConfig, 'url' | 'method'>> & Pick<AxiosRequestConfig<D>, 'data'>;
|
||||
export type PageRequestConfig<D = any> = RequiredConfigParams<D> & { locale?: string, token?: string };
|
||||
export type PageRequestConfig<D = any> = RequiredConfigParams<D> & Partial<Pick<AxiosRequestConfig, 'headers'>> & { locale?: string, token?: string };
|
||||
|
||||
export const apiRequest = async <T = any, K = any>(
|
||||
baseParams: PageRequestConfig<T>,
|
||||
|
@ -15,29 +15,30 @@ export const apiRequest = async <T = any, K = any>(
|
|||
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<K> = await apiClient.request<any, AxiosResponse<K>, T>(config as AxiosRequestConfig<T>);
|
||||
|
||||
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,
|
||||
// }),
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<Profile | undefined>();
|
||||
const [profileSettings, setProfileSettings] = useState<ProfileData | undefined>();
|
||||
const [fetchLoading, setFetchLoading] = useState<boolean>(false);
|
||||
const [saveLoading, setSaveLoading] = useState<boolean>(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
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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<Profile> => apiRequest({
|
||||
url: '/home/userdata',
|
||||
method: 'post',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const getPersonalData = (locale: string, token: string): Promise<ProfileData> => 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<Profile> => apiRequest({
|
||||
url: '/home/userdata',
|
||||
export const getEducation = (locale: string, token: string): Promise<EducationDTO> => apiRequest({
|
||||
url: '/home/person2',
|
||||
method: 'post',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const setEducation = (locale: string, token: string, data: EducationData): Promise<EducationData> => apiRequest({
|
||||
url: '/home/applyperson2',
|
||||
method: 'post',
|
||||
data,
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const getTags = (locale: string, token: string): Promise<ExpertsTags> => apiRequest({
|
||||
url: '/home/person3',
|
||||
method: 'post',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const setTags = (locale: string, token: string, data: ExpertsTags): Promise<ExpertsTags> => apiRequest({
|
||||
url: '/home/applyperson3',
|
||||
method: 'post',
|
||||
data,
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const getPractice = (locale: string, token: string): Promise<PracticeDTO> => apiRequest({
|
||||
url: '/home/person4',
|
||||
method: 'post',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const setPractice = (locale: string, token: string, data: PracticeData): Promise<PracticeDTO> => apiRequest({
|
||||
url: '/home/applyperson4',
|
||||
method: 'post',
|
||||
data,
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const getSchedule = (locale: string, token: string): Promise<ScheduleDTO> => apiRequest({
|
||||
url: '/home/person51',
|
||||
method: 'post',
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
||||
export const setSchedule = (locale: string, token: string, data: ScheduleDTO): Promise<ScheduleDTO> => 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<PayInfo> => apiRequest({
|
||||
url: '/home/applyperson6',
|
||||
method: 'post',
|
||||
data,
|
||||
locale,
|
||||
token
|
||||
});
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
import { ExpertDocument } from '../types/file';
|
||||
import { apiRequest } from './helpers';
|
||||
|
||||
export const setUploadFile = (locale: string, token: string, data: any): Promise<ExpertDocument> => apiRequest({
|
||||
url: '/home/uploadfile',
|
||||
method: 'post',
|
||||
data,
|
||||
locale,
|
||||
token,
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
});
|
|
@ -7,12 +7,12 @@ export default function AddOffer() {
|
|||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/work-with-us' as any}>
|
||||
<Link href={'/account/expert-profile' as any}>
|
||||
Work With Us
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/work-with-us/coaching' as any}>
|
||||
<Link href={'/account/expert-profile/coaching' as any}>
|
||||
Coaching
|
||||
</Link>
|
||||
</li>
|
|
@ -7,7 +7,7 @@ export default function NewTopic() {
|
|||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/work-with-us' as any}>
|
||||
<Link href={'/account/expert-profile' as any}>
|
||||
Work With Us
|
||||
</Link>
|
||||
</li>
|
|
@ -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<boolean>(false);
|
||||
const [data, setData] = useState<ExpertData | undefined>();
|
||||
|
||||
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 (
|
||||
<Loader isLoading={loading}>
|
||||
{data && (
|
||||
<ExpertProfile
|
||||
locale={locale}
|
||||
data={data}
|
||||
updateData={setData}
|
||||
/>
|
||||
)}
|
||||
</Loader>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<>
|
||||
|
|
|
@ -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 (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={'/account/work-with-us' as any}>
|
||||
Work With Us
|
||||
</Link>
|
||||
</li>
|
||||
<li className="breadcrumb-item active" aria-current="page">Coaching</li>
|
||||
</ol>
|
||||
<div className="coaching-info">
|
||||
<div className="card-profile">
|
||||
<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 className="card-profile__header__name">
|
||||
David
|
||||
</div>
|
||||
<div className="card-profile__header__title">
|
||||
12 Practice hours
|
||||
</div>
|
||||
<div className="card-profile__header__title ">
|
||||
15 Supervision per year
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-info__wrap-btn">
|
||||
<a href="#" className="btn-edit">Edit</a>
|
||||
<a href="#" className="btn-apply">Add Offer</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
My Offers
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="coaching-offer">
|
||||
<div className="coaching-offer__header">
|
||||
<div className="coaching-offer__title">
|
||||
Senior Software Engineer
|
||||
</div>
|
||||
<div className="coaching-offer__wrap-btn">
|
||||
<a href="#" className="link-edit">Edit</a>
|
||||
<a href="#" className="link-remove">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-offer__price">
|
||||
45$ <span>/ 45min</span>
|
||||
</div>
|
||||
<div className="skills__list">
|
||||
<div className="skills__list__item">Engineering & Data</div>
|
||||
<div className="skills__list__item">Engineering & Data</div>
|
||||
<div className="skills__list__more">+6</div>
|
||||
</div>
|
||||
<div className="coaching-offer__desc">
|
||||
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 …
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
About Coach
|
||||
</h2>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
Education
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<h3 className="title-h3">Psychologist </h3>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
<div className="sertific">
|
||||
<img src="/images/sertific.png" className="" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('profCertification', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
Trainings | Seminars | Courses
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
MBA Information
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.work-with-us', locale)}</li>
|
||||
</ol>
|
||||
<div className="b-work">
|
||||
<div className="image-info">
|
||||
<img className="" src="/images/info.png" alt="" />
|
||||
</div>
|
||||
<div className="b-work__description">
|
||||
<div className="b-work__text">{i18nText('insertInfo', locale)}</div>
|
||||
<div className="b-work__text">{i18nText('changeUserData', locale)}</div>
|
||||
<button className="btn-apply">{i18nText('getStarted', locale)}</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -6,4 +6,4 @@ export default function Loading() {
|
|||
...loading
|
||||
</div>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
|
||||
|
||||
export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
|
||||
const [form] = Form.useForm<Profile>();
|
||||
const { profileSettings } = useProfileSettings(locale);
|
||||
const [form] = Form.useForm<ProfileRequest>();
|
||||
const { profileSettings, fetchProfileSettings, save, fetchLoading } = useProfileSettings(locale);
|
||||
const [showDeleteModal, setShowDeleteModal] = useState<boolean>(false);
|
||||
const [saveLoading, setSaveLoading] = useState<boolean>(false);
|
||||
const [photo, setPhoto] = useState<UploadFile | undefined>();
|
||||
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<UploadFile[]>();
|
||||
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 (
|
||||
<Form form={form} className="form-settings">
|
||||
<div className="user-avatar">
|
||||
<div className="user-avatar__edit" style={profileSettings?.faceImageUrl ? { backgroundImage: `url(${profileSettings.faceImageUrl})` } : undefined}>
|
||||
<input className="" type="file" id="input-file" />
|
||||
<label htmlFor="input-file" className="form-label" />
|
||||
<Loader isLoading={fetchLoading} refresh={fetchProfileSettings}>
|
||||
<Form form={form} className="form-settings">
|
||||
<div className="user-avatar">
|
||||
<div className="user-avatar__edit" style={profileSettings?.faceImageUrl ? { backgroundImage: `url(${profileSettings.faceImageUrl})` } : undefined}>
|
||||
<input className="" type="file" id="input-file" />
|
||||
<label htmlFor="input-file" className="form-label" />
|
||||
</div>
|
||||
<div className="user-avatar__text">{i18nText('photoDesc', locale)}</div>
|
||||
</div>
|
||||
<div className="user-avatar__text">{i18nText('photoDesc', locale)}</div>
|
||||
</div>
|
||||
{/* <ImgCrop rotationSlider>
|
||||
<Upload
|
||||
action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
|
||||
fileList={fileList}
|
||||
onChange={onChange}
|
||||
onPreview={onPreview}
|
||||
<ImgCrop
|
||||
modalTitle="Редактировать"
|
||||
modalOk="Сохранить"
|
||||
modalCancel="Отмена"
|
||||
beforeCrop={beforeCrop}
|
||||
>
|
||||
<Button icon={<CameraOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</ImgCrop> */}
|
||||
<div className="form-fieldset">
|
||||
<div className="form-field">
|
||||
<Form.Item name="username">
|
||||
<CustomInput placeholder={i18nText('name', locale)} />
|
||||
<Upload
|
||||
fileList={photo ? [photo] : profileSettings?.faceImageUrl ? [
|
||||
{
|
||||
uid: profileSettings.faceImageUrl,
|
||||
name: profileSettings.faceImageUrl,
|
||||
status: 'done',
|
||||
url: profileSettings.faceImageUrl
|
||||
}
|
||||
] : undefined}
|
||||
accept=".jpg,.jpeg,.png,.gif"
|
||||
beforeUpload={beforeUpload}
|
||||
multiple={false}
|
||||
showUploadList={false}
|
||||
>
|
||||
{photo && <img height={100} width={100} src={URL.createObjectURL(photo)} />}
|
||||
<Button icon={<CameraOutlined />}>Click to Upload</Button>
|
||||
</Upload>
|
||||
</ImgCrop>
|
||||
<div className="form-fieldset">
|
||||
<div className="form-field">
|
||||
<Form.Item name="username" rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Поле не должно быть пустым'
|
||||
}
|
||||
]}>
|
||||
<CustomInput placeholder={i18nText('name', locale)} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<Form.Item name="surname">
|
||||
<CustomInput placeholder={i18nText('surname', locale)} />
|
||||
</Form.Item>
|
||||
</div>
|
||||
{/* <div className="form-field">
|
||||
<Form.Item name="birthday">
|
||||
<CustomInput placeholder={i18nText('birthday', locale)} />
|
||||
</Form.Item>
|
||||
</div> */}
|
||||
<div className="form-field">
|
||||
<Form.Item name="login" rules={[
|
||||
{
|
||||
required: true,
|
||||
message: 'Поле не должно быть пустым'
|
||||
}
|
||||
]}>
|
||||
<CustomInput type="email" placeholder="E-mail" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<Form.Item name="surname">
|
||||
<CustomInput placeholder={i18nText('surname', locale)} />
|
||||
</Form.Item>
|
||||
<div className="form-actions">
|
||||
<FilledYellowButton
|
||||
onClick={onSaveProfile}
|
||||
loading={saveLoading}
|
||||
>
|
||||
{i18nText('save', locale)}
|
||||
</FilledYellowButton>
|
||||
<OutlinedButton onClick={() => router.push('change-password')}>
|
||||
{i18nText('changePass', locale)}
|
||||
</OutlinedButton>
|
||||
<OutlinedButton
|
||||
onClick={onDeleteAccount}
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
>
|
||||
{i18nText('deleteAcc', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
{/* <div className="form-field">
|
||||
<Form.Item name="birthday">
|
||||
<CustomInput placeholder={i18nText('birthday', locale)} />
|
||||
</Form.Item>
|
||||
</div> */}
|
||||
<div className="form-field">
|
||||
<Form.Item name="login">
|
||||
<CustomInput type="email" placeholder="E-mail" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
<div className="form-actions">
|
||||
<FilledYellowButton onClick={saveProfileSettings}>{i18nText('save', locale)}</FilledYellowButton>
|
||||
<OutlinedButton onClick={() => router.push('change-password')}>
|
||||
{i18nText('changePass', locale)}
|
||||
</OutlinedButton>
|
||||
<OutlinedButton
|
||||
onClick={onDeleteAccount}
|
||||
icon={<DeleteOutlined />}
|
||||
danger
|
||||
>
|
||||
{i18nText('deleteAcc', locale)}
|
||||
</OutlinedButton>
|
||||
</div>
|
||||
<DeleteAccountModal
|
||||
open={showDeleteModal}
|
||||
handleCancel={() => setShowDeleteModal(false)}
|
||||
/>
|
||||
</Form>
|
||||
<DeleteAccountModal
|
||||
open={showDeleteModal}
|
||||
handleCancel={() => setShowDeleteModal(false)}
|
||||
/>
|
||||
</Form>
|
||||
</Loader>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -283,7 +283,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio
|
|||
))}
|
||||
{(isCoach ? session?.clientComments : session?.coachComments)?.length > 0 && (
|
||||
<div className="card-detail__comments_title">
|
||||
{isCoach ? 'Client Comments' : 'Coach Comments'}
|
||||
{isCoach ? i18nText('session.clientComments', locale) : i18nText('session.coachComments', locale)}
|
||||
</div>
|
||||
)}
|
||||
{(isCoach ? session?.clientComments : session?.coachComments)?.map(({ id , comment }) => (
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
import { i18nText } from '../../i18nKeys';
|
||||
|
||||
export const EmptyExpertProfile = ({ locale }: { locale: string }) => (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.expert-profile', locale)}</li>
|
||||
</ol>
|
||||
<div className="b-work">
|
||||
<div className="image-info">
|
||||
<img className="" src="/images/info.png" alt="" />
|
||||
</div>
|
||||
<div className="b-work__description">
|
||||
<div className="b-work__text">{i18nText('insertInfo', locale)}</div>
|
||||
<div className="b-work__text">{i18nText('changeUserData', locale)}</div>
|
||||
<button className="btn-apply">{i18nText('getStarted', locale)}</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
|
@ -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 (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item active" aria-current="page">{i18nText('coaching', locale)}</li>
|
||||
</ol>
|
||||
<div className="coaching-info">
|
||||
<div className="coaching-profile">
|
||||
<div className="coaching-profile__portrait">
|
||||
<img src="/images/person.png" className="" alt="" />
|
||||
</div>
|
||||
<div className="coaching-profile__inner">
|
||||
<div className="coaching-profile__name">
|
||||
David
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('aboutCoach', locale)}</h2>
|
||||
<h2 className="title-h2">person1 + person4</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
/>
|
||||
</div>
|
||||
<div className="card-profile__header__title">
|
||||
{`12 ${i18nText('practiceHours', locale)}`}
|
||||
</div>
|
||||
<div className="card-profile__header__title ">
|
||||
{`15 ${i18nText('supervisionCount', locale)}`}
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Loader isLoading={loading.includes('tags')}>
|
||||
<ExpertTags
|
||||
locale={locale}
|
||||
data={data?.tags}
|
||||
updateExpert={updateExpert}
|
||||
/>
|
||||
</Loader>
|
||||
<ExpertSchedule locale={locale} data={data?.schedule} />
|
||||
<ExpertEducation locale={locale} data={data?.education} />
|
||||
<ExpertPayData locale={locale} data={data?.payData?.person6Data} />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
export const MyOffers = () => (
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
My Offers
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="coaching-offer">
|
||||
<div className="coaching-offer__header">
|
||||
<div className="coaching-offer__title">
|
||||
Senior Software Engineer
|
||||
</div>
|
||||
<div className="coaching-offer__wrap-btn">
|
||||
<a href="#" className="link-edit">Edit</a>
|
||||
<a href="#" className="link-remove">Remove</a>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-offer__price">
|
||||
45$ <span>/ 45min</span>
|
||||
</div>
|
||||
<div className="skills__list">
|
||||
<div className="skills__list__item">Engineering & Data</div>
|
||||
<div className="skills__list__item">Engineering & Data</div>
|
||||
<div className="skills__list__more">+6</div>
|
||||
</div>
|
||||
<div className="coaching-offer__desc">
|
||||
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 …
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
|
@ -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 (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('education', locale)}</h2>
|
||||
<h2 className="title-h2">person2</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
/>
|
||||
</div>
|
||||
<div className="coaching-section__desc">
|
||||
<h3 className="title-h3">Psychologist</h3>
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
<div className="sertific">
|
||||
<img src="/images/sertific.png" className="" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('profCertification', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">
|
||||
{`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`}
|
||||
</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="coaching-section">
|
||||
<h2 className="title-h2">{i18nText('mba', locale)}</h2>
|
||||
<div className="coaching-section__desc">
|
||||
<div className="base-text">
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra
|
||||
malesuada, ligula sem tempor risus, non posuere urna diam a libero.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">Card data - person6</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
/>
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Card
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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 (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">Schedule - person51</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
/>
|
||||
</div>
|
||||
<div className="base-text">
|
||||
Schedule
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -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<boolean>(false);
|
||||
|
||||
return (
|
||||
<div className="coaching-section__wrap">
|
||||
<div className="coaching-section">
|
||||
<div className="coaching-section__title">
|
||||
<h2 className="title-h2">{i18nText('direction', locale)}</h2>
|
||||
<LinkButton
|
||||
type="link"
|
||||
icon={<EditOutlined />}
|
||||
onClick={() => setShowEdit(true)}
|
||||
/>
|
||||
</div>
|
||||
<div className="skills__list">
|
||||
{data?.themesTags && data.themesTags?.length > 0 && data.themesTags
|
||||
.filter(({ isActive, isSelected }) => isActive && isSelected)
|
||||
.map(({ id, name }) => <Tag key={id} className="skills__list__item">{name}</Tag>)}
|
||||
</div>
|
||||
</div>
|
||||
<EditExpertTagsModal
|
||||
locale={locale}
|
||||
open={showEdit}
|
||||
data={data}
|
||||
handleCancel={() => setShowEdit(false)}
|
||||
refresh={() => updateExpert('tags')}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -0,0 +1,5 @@
|
|||
'use client'
|
||||
|
||||
export * from './EmptyExpertProfile';
|
||||
export * from './ExpertProfile';
|
||||
export * from './MyOffers';
|
|
@ -98,7 +98,7 @@ export const ExpertPractice: FC<ExpertDetailsProps> = ({ expert, locale }) => {
|
|||
|
||||
return practiceCases?.length > 0 ? (
|
||||
<div>
|
||||
<h3 className="title-h3">Successful Cases From Practice</h3>
|
||||
<h3 className="title-h3">{i18nText('successfulCase', locale)}</h3>
|
||||
{practiceCases?.map(({ id, description, themesGroupIds }) => {
|
||||
const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id));
|
||||
|
||||
|
|
|
@ -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<EditExpertTagsModalProps> = ({
|
||||
open,
|
||||
handleCancel,
|
||||
locale,
|
||||
data,
|
||||
refresh
|
||||
}) => {
|
||||
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
const [expertTags, setExpertTags] = useState<Tag[]>([]);
|
||||
|
||||
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 (
|
||||
<Modal
|
||||
className="b-modal"
|
||||
open={open}
|
||||
title={undefined}
|
||||
onOk={undefined}
|
||||
onCancel={handleCancel}
|
||||
footer={false}
|
||||
width={498}
|
||||
closeIcon={<CloseOutlined style={{ fontSize: 20, color: '#000' }}/>}
|
||||
>
|
||||
<div className="b-modal__expert__content">
|
||||
<div className="b-modal__expert__title">{i18nText('direction', locale)}</div>
|
||||
<div className="b-modal__expert__inner">
|
||||
{data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => (
|
||||
<div key={`group_${id}`}>
|
||||
<h3 className="title-h4">{name}</h3>
|
||||
{expertTags?.length > 0 ? (
|
||||
<div className="b-filter__inner">
|
||||
<List
|
||||
itemLayout="vertical"
|
||||
size="small"
|
||||
dataSource={expertTags.filter(({ isActive, groupId }) => (isActive && groupId == id)) || []}
|
||||
split={false}
|
||||
style={{ width: '100%' }}
|
||||
renderItem={({ id, name, isSelected }) => (
|
||||
<List.Item key={`tag_${id}`} style={{ padding: 0 }}>
|
||||
<div className="b-filter__item">
|
||||
<div className="b-filter__title">{name}</div>
|
||||
<CustomSwitch
|
||||
defaultChecked={isSelected || false}
|
||||
onChange={(checked: boolean) => updateTag(id, checked)}
|
||||
/>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
) : <div>No tags</div>}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div className="b-modal__expert__button">
|
||||
<Button
|
||||
className="card-detail__apply"
|
||||
onClick={onSaveTags}
|
||||
loading={loading}
|
||||
>
|
||||
{i18nText('save', locale)}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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[]
|
||||
}
|
|
@ -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[];
|
||||
};
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
export type File = {
|
||||
id: number;
|
||||
fileType: string;
|
||||
url: string;
|
||||
};
|
||||
|
||||
export interface ExpertDocument {
|
||||
fileName: string;
|
||||
original?: File;
|
||||
preview?: File;
|
||||
fullSize?: File;
|
||||
}
|
|
@ -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[]
|
||||
}
|
||||
}
|
|
@ -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 },
|
||||
}
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export type WorkingTime = {
|
||||
startDayOfWeekUtc?: string,
|
||||
startTimeUtc?: number,
|
||||
endDayOfWeekUtc?: string,
|
||||
endTimeUtc?: number
|
||||
}
|
||||
|
||||
export interface ScheduleDTO {
|
||||
workingTimes?: WorkingTime[]
|
||||
}
|
|
@ -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[]
|
||||
}
|
||||
|
|
|
@ -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<string, number> = {
|
||||
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;
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue