feat: add normal auth

This commit is contained in:
Сюткина Дарья Александровна (4047910) 2024-02-08 02:28:24 +04:00
parent 9f225294c7
commit bdd382042c
42 changed files with 1037 additions and 387 deletions

View File

@ -68,7 +68,6 @@
}, },
"Notifications": { "Notifications": {
"title": "Notifications", "title": "Notifications",
"read": "Read",
"delete": "Delete" "delete": "Delete"
}, },
"Sessions": { "Sessions": {

View File

@ -1,10 +1,22 @@
import { AxiosResponse } from 'axios'; import { AxiosResponse } from 'axios';
import { apiClient } from '../lib/apiClient'; import { apiClient } from '../lib/apiClient';
export const getAuth = (login: string, password: string, locale: string ): Promise<AxiosResponse<{ jwtToken: string }>> => ( export const getAuth = (locale: string, data: { login: string, password: string }): Promise<AxiosResponse<{ jwtToken: string }>> => (
apiClient.post( apiClient.post(
'/auth/login', '/auth/login',
{ login, password }, data,
{
headers: {
'X-User-Language': locale
}
}
)
);
export const getRegister = (locale: string): Promise<AxiosResponse<{ jwtToken: string }>> => (
apiClient.post(
'/auth/register',
{},
{ {
headers: { headers: {
'X-User-Language': locale 'X-User-Language': locale

15
src/actions/profile.ts Normal file
View File

@ -0,0 +1,15 @@
import { AxiosResponse } from 'axios';
import { apiClient } from '../lib/apiClient';
export const setPersonData = (person: { login: string, password: string, role: string }, locale: string, jwt: string): Promise<AxiosResponse> => (
apiClient.post(
'/home/applyperson1',
{ ...person },
{
headers: {
'X-User-Language': locale,
Authorization: `Bearer ${jwt}`
}
}
)
);

View File

@ -34,7 +34,7 @@ export default function Information() {
mentors and/or consultants of our application and/or website as well as other users of our services mentors and/or consultants of our application and/or website as well as other users of our services
and suppliers. and suppliers.
</div> </div>
<h3 className="title-h3">What Personal Date We Collect About You?</h3> <h3 className="title-h3">What Personal Data We Collect About You?</h3>
<div className="base-text"> <div className="base-text">
We may collect, use, store and transfer different kinds of personal data about you which we have grouped We may collect, use, store and transfer different kinds of personal data about you which we have grouped
together as follows:<br /> together as follows:<br />

View File

@ -0,0 +1,61 @@
import React, { ReactNode } from 'react';
import { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { AccountMenu } from '../../../../components/Account';
type AccountInnerLayoutProps = {
children: ReactNode;
params: Record<string, string>;
};
export const metadata: Metadata = {
title: 'Bbuddy User Account'
};
export function generateStaticParams({
params: { locale },
}: { params: { locale: string } }) {
const result: { locale: string, userId: string }[] = [];
const users = [{ userId: '1' }, { userId: '2' }];
users.forEach(({ userId }) => {
result.push({ locale, userId });
});
return result;
}
const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'work-with-us'];
const COUNTS: Record<string, number> = {
sessions: 12,
notifications: 5,
messages: 113
};
export default function AccountInnerLayout({ children }: AccountInnerLayoutProps) {
const t = useTranslations('Account');
const getMenuConfig = () => ROUTES.map((path) => ({
path,
title: t(`menu.${path}`),
count: COUNTS[path] || undefined
}));
return (
<div className="page-account">
<div className="b-inner">
<div className="row">
<div className="col-xl-3 col-lg-4 d-none d-lg-block">
<AccountMenu menu={getMenuConfig()} />
</div>
<div className="col-xl-9 col-lg-8 ">
<div className="page-account__inner">
{children}
</div>
</div>
</div>
</div>
</div>
);
};

View File

@ -1,28 +1,28 @@
import React from 'react'; import React from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Link } from '../../../../../navigation'; import { Link } from '../../../../../../navigation';
export function generateStaticParams({ export function generateStaticParams({
params: { locale, userId }, params: { locale },
}: { params: { locale: string, userId: string } }) { }: { params: { locale: string } }) {
const result: { locale: string, userId: string, textId: string }[] = []; const result: { locale: string, textId: string }[] = [];
const chats = [{ textId: '1' }, { textId: '2' }, { textId: '3' }]; const chats = [{ textId: '1' }, { textId: '2' }, { textId: '3' }];
chats.forEach(({ textId }) => { chats.forEach(({ textId }) => {
result.push({ locale, userId, textId }); result.push({ locale, textId });
}); });
return result; return result;
} }
export default function Message({ params }: { params: { userId: string, textId: string } }) { export default function Message({ params }: { params: { textId: string } }) {
const t = useTranslations('Account.Messages'); const t = useTranslations('Account.Messages');
return ( return (
<> <>
<ol className="breadcrumb"> <ol className="breadcrumb">
<li className="breadcrumb-item"> <li className="breadcrumb-item">
<Link href={`/${params.userId}/messages` as any}> <Link href={'/account/messages' as any}>
{t('title')} {t('title')}
</Link> </Link>
</li> </li>

View File

@ -1,14 +1,15 @@
import React from 'react'; import React, { Suspense } from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Link } from '../../../../navigation'; import { Link } from '../../../../../navigation';
import { CustomInput } from '../../../../../components/view';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Bbuddy - Account - Messages', title: 'Bbuddy - Account - Messages',
description: 'Bbuddy desc messages' description: 'Bbuddy desc messages'
}; };
export default function Messages({ params }: { params: { userId: string } }) { export default function Messages() {
const t = useTranslations('Account.Messages'); const t = useTranslations('Account.Messages');
return ( return (
@ -16,10 +17,13 @@ export default function Messages({ params }: { params: { userId: string } }) {
<ol className="breadcrumb"> <ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li> <li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
</ol> </ol>
<Suspense>
<CustomInput placeholder="Name" />
</Suspense>
<div className="messages-session"> <div className="messages-session">
<Link <Link
className="card-profile" className="card-profile"
href={`/${params.userId}/messages/1` as any} href={'1' as any}
> >
<div className="card-profile__header"> <div className="card-profile__header">
<div className="card-profile__header__portrait"> <div className="card-profile__header__portrait">
@ -41,7 +45,7 @@ export default function Messages({ params }: { params: { userId: string } }) {
</Link> </Link>
<Link <Link
className="card-profile" className="card-profile"
href={`/${params.userId}/messages/2` as any} href={'2' as any}
> >
<div className="card-profile__header"> <div className="card-profile__header">
<div className="card-profile__header__portrait"> <div className="card-profile__header__portrait">
@ -60,7 +64,7 @@ export default function Messages({ params }: { params: { userId: string } }) {
</Link> </Link>
<Link <Link
className="card-profile" className="card-profile"
href={`/${params.userId}/messages/3` as any} href={'3' as any}
> >
<div className="card-profile__header"> <div className="card-profile__header">
<div className="card-profile__header__portrait"> <div className="card-profile__header__portrait">

View File

@ -23,7 +23,6 @@ export default function Notifications() {
</div> </div>
<div className="b-notifications__date">25 may 2022</div> <div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__inner"> <div className="b-notifications__inner">
<a href="#" className="b-notifications__read">{t('read')}</a>
<a href="#" className="b-notifications__delete">{t('delete')}</a> <a href="#" className="b-notifications__delete">{t('delete')}</a>
</div> </div>
</div> </div>
@ -34,7 +33,6 @@ export default function Notifications() {
</div> </div>
<div className="b-notifications__date">25 may 2022</div> <div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__inner"> <div className="b-notifications__inner">
<a href="#" className="b-notifications__read">{t('read')}</a>
<a href="#" className="b-notifications__delete">{t('delete')}</a> <a href="#" className="b-notifications__delete">{t('delete')}</a>
</div> </div>
</div> </div>
@ -45,7 +43,6 @@ export default function Notifications() {
</div> </div>
<div className="b-notifications__date">25 may 2022</div> <div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__inner"> <div className="b-notifications__inner">
<a href="#" className="b-notifications__read">{t('read')}</a>
<a href="#" className="b-notifications__delete">{t('delete')}</a> <a href="#" className="b-notifications__delete">{t('delete')}</a>
</div> </div>
</div> </div>

View File

@ -0,0 +1,27 @@
import React, { Suspense } from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { SessionsTabs } from '../../../../../components/Account';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Sessions',
description: 'Bbuddy desc sessions'
};
export default function Sessions() {
const t = useTranslations('Account.Sessions');
return (
<Suspense fallback={<p>Loading...</p>}>
<SessionsTabs
intlConfig={{
upcoming: t('upcoming-sessions'),
requested: t('sessions-requested'),
recent: t('recent-sessions'),
selectTopicLabel: t('topic'),
dateLabel: t('day-start')
}}
/>
</Suspense>
);
}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import { Link } from '../../../../../navigation'; import { Link } from '../../../../../../navigation';
export default function ChangePassword({ params }: { params: { userId: string } }) { export default function ChangePassword({ params }: { params: { userId: string } }) {
const t = useTranslations('Account.Settings'); const t = useTranslations('Account.Settings');

View File

@ -1,7 +1,8 @@
import React from 'react'; import React, {Suspense} from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { useTranslations } from 'next-intl'; import { useTranslations } from 'next-intl';
import {Link} from "../../../../navigation"; import { Link } from '../../../../../navigation';
import { CustomInput } from '../../../../../components/view';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Bbuddy - Account - Profile Settings', title: 'Bbuddy - Account - Profile Settings',
@ -25,19 +26,19 @@ export default function Settings({ params }: { params: { userId: string } }) {
<div className="user-avatar__text">{t('photo-desc')}</div> <div className="user-avatar__text">{t('photo-desc')}</div>
</div> </div>
<div className="form-field"> <div className="form-field">
<input type="text" placeholder={t('name')} className="base-input" id="" value="" /> <CustomInput placeholder={t('name')} />
</div> </div>
<div className="form-field"> <div className="form-field">
<input type="text" placeholder={t('surname')} className="base-input" id="" value="" /> <CustomInput placeholder={t('surname')} />
</div> </div>
<div className="form-field date"> <div className="form-field date">
<input className="base-input " type="text" placeholder={t('birthday')} id="" value="" /> <CustomInput placeholder={t('birthday')} />
</div> </div>
<div className="form-field"> <div className="form-field">
<input type="email" placeholder={t('email')} className="base-input" id="" value="" /> <CustomInput type="email" placeholder={t('email')} />
</div> </div>
<div className="form-link"> <div className="form-link">
<Link href={`/${params.userId}/settings/change-password` as any}> <Link href={'change-password' as any}>
{t('change-password')} {t('change-password')}
</Link> </Link>
</div> </div>

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import {useTranslations} from "next-intl"; import { useTranslations } from 'next-intl';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Bbuddy - Account - Help & Support', title: 'Bbuddy - Account - Help & Support',

View File

@ -0,0 +1,94 @@
import React from 'react';
import { Link } from '../../../../../../../navigation';
import { CustomSelect } from '../../../../../../../components/view';
export default function AddOffer() {
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">
<Link href={'/account/work-with-us/coaching' as any}>
Coaching
</Link>
</li>
<li className="breadcrumb-item active" aria-current="page">Add Offer</li>
</ol>
<h2 className="title-h2">Select Topic</h2>
<form className="form-offer" action="">
<div className="form-field ">
<input type="text" placeholder="Search" className="base-input" id="" value="" />
</div>
<h3 className="title-h3">Life coaching</h3>
<div className="b-filter">
<div className="b-filter__inner">
<div className="b-filter__item">
<div className="b-filter__title">Work-life Balance</div>
<label className="base-switcher">
<input className="base-switcher__input" type="checkbox" />
<span className="base-switcher__line" />
<span className="base-switcher__circle" />
</label>
</div>
<div className="b-filter__item">
<div className="b-filter__title">Strategic Session</div>
<label className="base-switcher">
<input className="base-switcher__input" type="checkbox" />
<span className="base-switcher__line" />
<span className="base-switcher__circle" />
</label>
</div>
<div className="b-filter__item">
<div className="b-filter__title">Personal Growth
</div>
<label className="base-switcher">
<input className="base-switcher__input" type="checkbox" />
<span className="base-switcher__line" />
<span className="base-switcher__circle" />
</label>
</div>
<div className="b-filter__item">
<div className="b-filter__title">Executive Coaching</div>
<label className="base-switcher">
<input className="base-switcher__input" type="checkbox" />
<span className="base-switcher__line" />
<span className="base-switcher__circle" />
</label>
</div>
<div className="b-filter__item">
<div className="b-filter__title">Career Planning</div>
<label className="base-switcher">
<input className="base-switcher__input" type="checkbox" />
<span className="base-switcher__line" />
<span className="base-switcher__circle" />
</label>
</div>
</div>
</div>
<h3 className="title-h3">Business coaching</h3>
<h2 className="title-h2">Offer Details</h2>
<div className="form-field">
<CustomSelect label="Session Language" />
</div>
<div className="form-field">
<input type="text" placeholder="Title" className="base-input" id="" value="" />
</div>
<div className="form-field">
<textarea className="base-textarea" name="" id="" placeholder="Description" />
</div>
<div className="form-field">
<CustomSelect label="Session Duration" />
</div>
<div className="form-field">
<input className="base-input " type="text" placeholder="Session cost in euro" id="" value="" />
</div>
<button className="btn-apply">Save</button>
</form>
</>
);
}

View File

@ -0,0 +1,131 @@
import React from 'react';
import { Link } from '../../../../../../navigation';
export default function Coaching() {
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">
Professional Certification
</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>
</>
);
}

View File

@ -0,0 +1,55 @@
import React from 'react';
import { Link } from '../../../../../../navigation';
import { CustomSelect } from '../../../../../../components/view';
export default function NewTopic() {
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">New Topic</li>
</ol>
<h2 className="title-h2">
Schedule
</h2>
<form action="">
<div className="schedule">
<div className="schedule__inner">
<div className="timezone">
<div className="timezone__title">Your timezone: +06:00</div>
<div className="timezone__utc">
<CustomSelect label="UTC" />
</div>
</div>
<h3 className="title-h3">Fill up your weekly schedule</h3>
<div className="">место для календаря на неделю...</div>
<h3 className="title-h3">Work time</h3>
<div className="schedule__wrap">
<CustomSelect label="Start at" />
<CustomSelect label="Finish at" />
<div className="btn-delete" />
</div>
<button className="btn-add">Add Working Hours</button>
</div>
<div className="separator" />
<div className="schedule__inner">
<h2 className="title-h2">Specialisation</h2>
<CustomSelect label="I'm best in" />
</div>
<div className="b-info">
<div className="image-info">
<img className="" src="/images/info.png" alt=""/>
</div>
<div className="b-info__title">
Select your specialisation to proceed
</div>
</div>
</div>
</form>
</>
);
}

View File

@ -1,6 +1,6 @@
import React from 'react'; import React from 'react';
import type { Metadata } from 'next'; import type { Metadata } from 'next';
import { useTranslations } from "next-intl"; import { useTranslations } from 'next-intl';
export const metadata: Metadata = { export const metadata: Metadata = {
title: 'Bbuddy - Account - Work with us', title: 'Bbuddy - Account - Work with us',

View File

@ -1,64 +1,18 @@
import React, { ReactNode } from 'react'; 'use client';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import { useTranslations } from 'next-intl';
import { AccountMenu } from '../../../components/Account';
type AccountLayoutProps = { import { ReactNode, useEffect } from 'react';
children: ReactNode; import { redirect, notFound } from 'next/navigation';
params: Record<string, string>; import { useLocalStorage } from '../../../hooks/useLocalStorage';
}; import { AUTH_TOKEN_KEY } from '../../../constants/common';
export const metadata: Metadata = { export default function AccountLayout({ children }: { children: ReactNode }) {
title: 'Bbuddy User Account' const [token] = useLocalStorage(AUTH_TOKEN_KEY, '');
};
useEffect(() => {
export function generateStaticParams({ if(!token){
params: { locale }, notFound();
}: { params: { locale: string } }) { }
const result: { locale: string, userId: string }[] = []; }, []);
const users = [{ userId: '1' }, { userId: '2' }];
return children;
users.forEach(({ userId }) => {
result.push({ locale, userId });
});
return result;
}
const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'work-with-us'];
const COUNTS: Record<string, number> = {
sessions: 12,
notifications: 5,
messages: 113
};
export default function AccountLayout({ children, params }: AccountLayoutProps) {
if (!params?.userId || isNaN(Number(params?.userId))) notFound();
const t = useTranslations('Account');
const getMenuConfig = () => ROUTES.map((path) => ({
path,
title: t(`menu.${path}`),
count: COUNTS[path] || undefined
}));
return (
<div className="page-account">
<div className="b-inner">
<div className="row">
<div className="col-xl-3 col-lg-4 d-none d-lg-block">
<AccountMenu userId={params.userId} menu={getMenuConfig()} />
</div>
<div className="col-xl-9 col-lg-8 ">
<div className="page-account__inner">
{children}
</div>
</div>
</div>
</div>
</div>
);
}; };

View File

@ -1,12 +1,12 @@
import { redirect, notFound } from 'next/navigation'; 'use client';
// import { checkAuthToken } from '../../../utils/storage/auth';
export default function AccountMainPage() { import React from 'react';
// if (checkAuthToken()) { import { redirect } from 'next/navigation';
// redirect('/sessions'); import { useLocalStorage } from '../../../hooks/useLocalStorage';
// } else { import { AUTH_TOKEN_KEY } from '../../../constants/common';
// notFound();
// }
redirect('/sessions'); export default function Account() {
const [token] = useLocalStorage(AUTH_TOKEN_KEY, '');
return token ? redirect('sessions') : null;
}; };

View File

@ -1,25 +0,0 @@
import React from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { SessionsTabs } from '../../../../components/Account';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Sessions',
description: 'Bbuddy desc sessions'
};
export default function Sessions() {
const t = useTranslations('Account.Sessions');
return (
<SessionsTabs
intlConfig={{
upcoming: t('upcoming-sessions'),
requested: t('sessions-requested'),
recent: t('recent-sessions'),
selectTopicLabel: t('topic'),
dateLabel: t('day-start')
}}
/>
);
}

View File

@ -1,18 +1,39 @@
'use client'; 'use client';
import React from 'react'; import React from 'react';
import { useSelectedLayoutSegment } from 'next/navigation'; import styled from 'styled-components';
import { Button } from 'antd';
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
import { Link } from '../../navigation'; import { Link } from '../../navigation';
import { AUTH_TOKEN_KEY } from '../../constants/common';
import { deleteStorageKey } from '../../hooks/useLocalStorage';
export const AccountMenu = ({ userId, menu }: { userId: string, menu: { path: string, title: string, count?: number }[] }) => { const Logout = styled(Button)`
width: 100%;
height: 49px !important;
color: #D93E5C !important;
font-style: normal;
font-weight: 600 !important;
padding: 0 !important;
font-size: 1.125rem !important;
text-align: left !important;
`;
export const AccountMenu = ({ menu }: { menu: { path: string, title: string, count?: number }[] }) => {
const selectedLayoutSegment = useSelectedLayoutSegment(); const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment || ''; const pathname = selectedLayoutSegment || '';
const paths = usePathname();
const onLogout = () => {
deleteStorageKey(AUTH_TOKEN_KEY);
window?.location?.replace(`/${paths.split('/')[1]}/`);
};
return ( return (
<ul className="list-sidebar"> <ul className="list-sidebar">
{menu.map(({ path, title, count }) => ( {menu.map(({ path, title, count }) => (
<li key={path} className="list-sidebar__item"> <li key={path} className="list-sidebar__item">
<Link href={`/${userId}/${path}` as any} className={path === pathname ? 'active' : ''}> <Link href={`/account/${path}` as any} className={path === pathname ? 'active' : ''}>
{title} {title}
{count ? ( {count ? (
<span className="count">{count}</span> <span className="count">{count}</span>
@ -21,7 +42,12 @@ export const AccountMenu = ({ userId, menu }: { userId: string, menu: { path: st
</li> </li>
))} ))}
<li className="list-sidebar__item"> <li className="list-sidebar__item">
<a href="#" className="">Log Out</a> <Logout
type="link"
onClick={onLogout}
>
Log Out
</Logout>
</li> </li>
</ul> </ul>
) )

View File

@ -1,19 +1,35 @@
'use client'; 'use client';
import React from 'react'; import React, { useCallback, useState } from 'react';
import { Tabs } from 'antd'; import styled from 'styled-components';
import { CustomSelect } from '../view';
const Tab = styled.div``;
export const SessionsTabs = ({ intlConfig }: { intlConfig: Record<string, string> }) => { export const SessionsTabs = ({ intlConfig }: { intlConfig: Record<string, string> }) => {
const [activeTab, setActiveTab] = useState<number>(0);
const [sort, setSort] = useState<string>();
const onChangeSort = useCallback((value: string) => {
setSort(value);
}, [sort]);
const getChildren = () => ( const getChildren = () => (
<> <>
<div className="filter-session"> <div className="filter-session">
<div className="filter-session__item filter-session__item--type"> <div className="filter-session__item">
<select name="" id=""> <CustomSelect
<option value="">1</option> label="Topic"
<option value="">2</option> value={sort}
</select> onChange={onChangeSort}
options={[
{ value: 'topic1', label: 'Topic 1' },
{ value: 'topic2', label: 'Topic 2' },
{ value: 'topic3', label: 'Topic 3' },
{ value: 'topic4', label: 'Topic 4' }
]}
/>
</div> </div>
</div> </div>
<div className="list-session"> <div className="list-session">
<div className="card-profile"> <div className="card-profile">
@ -72,39 +88,48 @@ export const SessionsTabs = ({ intlConfig }: { intlConfig: Record<string, string
{ {
key: 'upcoming', key: 'upcoming',
label: ( label: (
<div className="tabs-session__item active"> <>
{intlConfig?.upcoming || 'Tab 1'} {intlConfig?.upcoming || 'Tab 1'}
<span className="count">3</span> <span className="count">3</span>
</div> </>
), ),
children: getChildren() children: getChildren()
}, },
{ {
key: 'requested', key: 'requested',
label: ( label: (
<div className="tabs-session__item"> <>
{intlConfig?.requested || 'Tab 2'} {intlConfig?.requested || 'Tab 2'}
<span className="count">2</span> <span className="count">2</span>
</div> </>
), ),
children: getChildren() children: getChildren()
}, },
{ {
key: 'recent', key: 'recent',
label: ( label: (
<div className="tabs-session__item"> <>
{intlConfig?.recent || 'Tab 3'} {intlConfig?.recent || 'Tab 3'}
</div> </>
), ),
children: getChildren() children: getChildren()
} }
]; ];
return ( return (
<Tabs <>
defaultActiveKey="upcoming" <div className="tabs-session">
items={tabs} {tabs.map((tab, index) => (
renderTabBar={(props, DefaultTabBar) => (<DefaultTabBar {...props} className="tabs-session" />)} <Tab
/> key={index}
className={`tabs-session__item ${index === activeTab ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.label}
</Tab>
))}
</div>
{tabs[activeTab].children}
</>
); );
}; };

View File

@ -47,7 +47,7 @@ export const ExpertsList = ({
} else { } else {
setExperts(data); setExperts(data);
} }
}, []); }, [searchParams]);
return experts ? ( return experts ? (
<> <>

View File

@ -2,11 +2,11 @@
import React, { useCallback, useState } from 'react'; import React, { useCallback, useState } from 'react';
import { Button, List } from 'antd'; import { Button, List } from 'antd';
import { useSearchParams } from 'next/navigation'; import { useSearchParams, usePathname } from 'next/navigation';
import { Filter } from '../../types/experts';
import { SearchData, Tag } from '../../types/tags';
import { useRouter } from '../../navigation'; import { useRouter } from '../../navigation';
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter'; import { Filter, GeneralFilter } from '../../types/experts';
import { SearchData, Tag } from '../../types/tags';
import { getObjectByFilter, getObjectByAdditionalFilter, getSearchParamsString } from '../../utils/filter';
import { CustomSwitch, CustomSlider } from '../view'; import { CustomSwitch, CustomSlider } from '../view';
type ExpertsFilterProps = { type ExpertsFilterProps = {
@ -26,6 +26,7 @@ export const ExpertsFilter = ({
}: ExpertsFilterProps) => { }: ExpertsFilterProps) => {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const router = useRouter(); const router = useRouter();
const pathname = usePathname();
const [filter, setFilter] = useState<Filter | undefined>(getObjectByFilter(searchParams)); const [filter, setFilter] = useState<Filter | undefined>(getObjectByFilter(searchParams));
const onChangeTags = useCallback((id: number, checked: boolean) => { const onChangeTags = useCallback((id: number, checked: boolean) => {
@ -82,6 +83,14 @@ export const ExpertsFilter = ({
}, [filter, searchParams, searchData]); }, [filter, searchParams, searchData]);
const goToFilterPage = useCallback(() => { const goToFilterPage = useCallback(() => {
// const newFilter: GeneralFilter = {
// ...filter,
// ...getObjectByAdditionalFilter(searchParams)
// };
// const search = getSearchParamsString(newFilter);
//
// router.push(search ? `${pathname}?${search}` : pathname, { scroll: false });
router.push({ router.push({
pathname: basePath as any, pathname: basePath as any,
query: { query: {

View File

@ -1,21 +1,19 @@
'use client'; 'use client';
import React, { FC, useState } from 'react'; import React, { Dispatch, FC, SetStateAction, useEffect } from 'react';
import { usePathname } from 'next/navigation'; import { usePathname } from 'next/navigation';
import Image from 'next/image';
import Link from 'next/link'; import Link from 'next/link';
import {Button, Modal as AntdModal, Form, notification} from 'antd'; import { Modal as AntdModal, Form } from 'antd';
import { CloseOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons';
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { CustomInput, CustomInputPassword } from '../view'; import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent';
import { getAuth } from '../../actions/auth';
import { setAuthToken } from '../../utils/storage/auth';
type AuthModalProps = { type AuthModalProps = {
open: boolean; open: boolean;
handleCancel: () => void; handleCancel: () => void;
mode: 'enter' | 'register' | 'reset' | 'finish'; mode: 'enter' | 'register' | 'reset' | 'finish';
updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void; updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void;
updateToken: string | Dispatch<SetStateAction<string | undefined>> | undefined;
}; };
const Modal = styled(AntdModal)` const Modal = styled(AntdModal)`
@ -37,84 +35,37 @@ const Modal = styled(AntdModal)`
} }
`; `;
const FilledButton = styled(Button)`
background: #66A5AD !important;
font-size: 15px !important;
border-radius: 8px !important;
height: 54px !important;
box-shadow: 0px 2px 4px 0px rgba(102, 165, 173, 0.32) !important;
`;
const OutlinedButton = styled(Button)`
display: inline-flex !important;
justify-content: center;
align-items: center;
gap: 16px;
border-color: #66A5AD !important;
color: #66A5AD !important;
font-size: 15px !important;
border-radius: 8px !important;
height: 54px !important;
span {
margin-inline-end: 0 !important;
line-height: 15px !important;
}
`;
const LinkButton = styled(Button)`
color: #66A5AD !important;
font-size: 15px !important;
height: auto !important;
padding: 0 !important;
`;
export const AuthModal: FC<AuthModalProps> = ({ export const AuthModal: FC<AuthModalProps> = ({
open, open,
handleCancel, handleCancel,
mode, mode,
updateMode updateMode,
updateToken
}) => { }) => {
const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>(); const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>();
const [isLoading, setIsLoading] = useState<boolean>(false);
const paths = usePathname().split('/'); const paths = usePathname().split('/');
const onAfterClose = () => { const onAfterClose = () => {
form.resetFields(); form.resetFields();
}; };
const onSubmit = () => { useEffect(() => {
form.validateFields().then(() => { if (form) {
const { login, password } = form.getFieldsValue(); form.resetFields();
setIsLoading(true); }
getAuth(login, password, paths[1]) }, [mode]);
.then(({ data }) => {
if (data.jwtToken) {
setAuthToken(data.jwtToken);
handleCancel();
}
})
.catch((error) => {
notification.error({
message: 'Error',
description: error?.response?.data?.errMessage
});
})
.finally(() => {
setIsLoading(false);
})
});
};
const onValidate = () => {
const onUpdateToken = (token: string) => {
if (updateToken && typeof updateToken !== 'string') {
updateToken(token);
}
}; };
return ( return (
<Modal <Modal
open={open} open={open}
title={undefined} title={undefined}
onOk={onSubmit} onOk={undefined}
onCancel={handleCancel} onCancel={handleCancel}
afterClose={onAfterClose} afterClose={onAfterClose}
footer={false} footer={false}
@ -126,124 +77,28 @@ export const AuthModal: FC<AuthModalProps> = ({
<img className="" src="/images/logo-auth.svg" alt=""/> <img className="" src="/images/logo-auth.svg" alt=""/>
</div> </div>
{mode === 'enter' && ( {mode === 'enter' && (
<> <EnterContent
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}> form={form}
<Form.Item name="login" rules={[{ required: true }]} noStyle> updateMode={updateMode}
<CustomInput locale={paths[1]}
size="small" handleCancel={handleCancel}
placeholder="E-mail" updateToken={onUpdateToken}
/> />
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]} noStyle>
<CustomInputPassword
size="small"
placeholder="Password"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
onClick={onSubmit}
loading={isLoading}
>
Enter
</FilledButton>
<OutlinedButton onClick={() => updateMode('register')}>Register</OutlinedButton>
<LinkButton
type="link"
onClick={() => updateMode('reset')}
>
Forgot password?
</LinkButton>
<span>or</span>
<OutlinedButton
icon={<Image src="/images/facebook-logo.png" height={20} width={20} alt="" />}
>
Facebook account
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
>
Apple account
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/google-logo.png" height={20} width={20} alt="" />}
>
Google account
</OutlinedButton>
</>
)} )}
{mode === 'register' && ( {mode === 'register' && (
<> <RegisterContent
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}> form={form}
<Form.Item name="login" rules={[{ required: true }]} noStyle> locale={paths[1]}
<CustomInput updateMode={updateMode}
size="small" updateToken={onUpdateToken}
placeholder="E-mail" handleCancel={handleCancel}
/> />
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]} noStyle>
<CustomInputPassword
size="small"
placeholder="Password"
/>
</Form.Item>
<Form.Item name="confirmPassword" rules={[{ required: true }]} noStyle>
<CustomInputPassword
size="small"
placeholder="Confirm password"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
>
Register
</FilledButton>
<OutlinedButton onClick={() => updateMode('enter')}>Enter</OutlinedButton>
</>
)} )}
{mode === 'reset' && ( {mode === 'reset' && (
<> <ResetContent form={form} updateMode={updateMode} />
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
<Form.Item name="login" rules={[{ required: true }]} noStyle>
<CustomInput
size="small"
placeholder="E-mail"
/>
</Form.Item>
<Form.Item name="password" rules={[{ required: true }]} noStyle>
<CustomInputPassword
size="small"
placeholder="Password"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
>
Reset Password
</FilledButton>
<LinkButton
type="link"
>
Enter
</LinkButton>
</>
)} )}
{mode === 'finish' && ( {mode === 'finish' && (
<> <FinishContent />
<div className="b-modal__auth__agreement">
A link to reset your password has been sent
<br />
to your email
</div>
<FilledButton
type="primary"
>
Enter Account
</FilledButton>
</>
)} )}
<div className="b-modal__auth__agreement"> <div className="b-modal__auth__agreement">
I have read and agree with the terms of the I have read and agree with the terms of the

View File

@ -0,0 +1,116 @@
import React, { FC, useState } from 'react';
import { Form, FormInstance, notification } from 'antd';
import Image from 'next/image';
import { getAuth } from '../../../actions/auth';
import { CustomInput, CustomInputPassword, FilledButton, OutlinedButton, LinkButton } from '../../view';
type EnterProps = {
form: FormInstance;
updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void;
locale: string;
handleCancel: () => void;
updateToken: (token: string) => void;
}
export const EnterContent: FC<EnterProps> = ({
form,
updateMode,
updateToken,
locale,
handleCancel
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const onLogin = () => {
form.validateFields().then(() => {
const { login, password } = form.getFieldsValue();
setIsLoading(true);
getAuth(locale, { login, password })
.then(({ data }) => {
if (data.jwtToken) {
updateToken(data.jwtToken);
handleCancel();
}
})
.catch((error) => {
notification.error({
message: 'Error',
description: error?.response?.data?.errMessage
});
})
.finally(() => {
setIsLoading(false);
})
});
};
return (
<>
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
<Form.Item
name="login"
noStyle
rules={[
{
type: 'email',
message: 'The input is not valid E-mail'
},
{
required: true,
message: 'Please input your E-mail'
}
]}
>
<CustomInput
size="small"
placeholder="E-mail"
type="email"
/>
</Form.Item>
<Form.Item
name="password"
noStyle
rules={[{
required: true,
message: 'Please input your password'
}]}
>
<CustomInputPassword
size="small"
placeholder="Password"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
onClick={onLogin}
loading={isLoading}
>
Enter
</FilledButton>
<OutlinedButton onClick={() => updateMode('register')}>Register</OutlinedButton>
<LinkButton
type="link"
onClick={() => updateMode('reset')}
>
Forgot password?
</LinkButton>
<span>or</span>
<OutlinedButton
icon={<Image src="/images/facebook-logo.png" height={20} width={20} alt="" />}
>
Facebook account
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
>
Apple account
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/google-logo.png" height={20} width={20} alt="" />}
>
Google account
</OutlinedButton>
</>
);
};

View File

@ -0,0 +1,17 @@
import React from 'react';
import { FilledButton } from '../../view';
export const FinishContent = () => (
<>
<div className="b-modal__auth__agreement">
A link to reset your password has been sent
<br />
to your email
</div>
<FilledButton
type="primary"
>
Enter Account
</FilledButton>
</>
);

View File

@ -0,0 +1,128 @@
import React, { FC, useState } from 'react';
import { Form, FormInstance, notification } from 'antd';
import { getRegister } from '../../../actions/auth';
import { setPersonData } from '../../../actions/profile';
import { CustomInput, CustomInputPassword, FilledButton, OutlinedButton } from '../../view';
type RegisterProps = {
form: FormInstance;
locale: string;
updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void;
updateToken: (token: string) => void;
handleCancel: () => void;
};
export const RegisterContent: FC<RegisterProps> = ({
form,
updateMode,
locale,
updateToken,
handleCancel
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const onRegister = () => {
form.validateFields().then(() => {
const { login, password } = form.getFieldsValue();
setIsLoading(true);
getRegister(locale)
.then(({ data }) => {
if (data.jwtToken) {
setPersonData( { login, password, role: 'client' }, locale, data.jwtToken)
.then(() => {
updateToken(data.jwtToken);
handleCancel();
})
.catch((error) => {
notification.error({
message: 'Error',
description: error?.response?.data?.errMessage
});
});
}
})
.catch((error) => {
notification.error({
message: 'Error',
description: error?.response?.data?.errMessage
});
})
.finally(() => {
setIsLoading(false);
})
});
};
return (
<>
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
<Form.Item
name="login"
noStyle
rules={[
{
type: 'email',
message: 'The input is not valid E-mail'
},
{
required: true,
message: 'Please input your E-mail'
}
]}
>
<CustomInput
size="small"
placeholder="E-mail"
type="email"
/>
</Form.Item>
<Form.Item
name="password"
noStyle
rules={[{
required: true,
message: 'Please input your password'
}]}
>
<CustomInputPassword
size="small"
placeholder="Password"
/>
</Form.Item>
<Form.Item
name="confirmPassword"
noStyle
dependencies={['password']}
hasFeedback
rules={[
{
required: true,
message: 'Please confirm your password',
},
({ getFieldValue }) => ({
validator(_, value) {
if (!value || getFieldValue('password') === value) {
return Promise.resolve();
}
return Promise.reject(new Error('The new password that you entered do not match'));
},
}),
]}
>
<CustomInputPassword
size="small"
placeholder="Confirm password"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
onClick={onRegister}
loading={isLoading}
>
Register
</FilledButton>
<OutlinedButton onClick={() => updateMode('enter')}>Enter</OutlinedButton>
</>
);
};

View File

@ -0,0 +1,56 @@
import React, { FC } from 'react';
import { Form, FormInstance } from 'antd';
import { CustomInput, FilledButton, LinkButton } from '../../view';
type ResetProps = {
form: FormInstance;
updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void;
}
export const ResetContent: FC<ResetProps> = ({
form,
updateMode
}) => {
const onResetPassword = () => {
console.log('reset');
};
return (
<>
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
<Form.Item
name="login"
noStyle
rules={[
{
type: 'email',
message: 'The input is not valid E-mail'
},
{
required: true,
message: 'Please input your E-mail'
}
]}
>
<CustomInput
size="small"
placeholder="E-mail"
type="email"
/>
</Form.Item>
</Form>
<FilledButton
type="primary"
onClick={onResetPassword}
>
Reset Password
</FilledButton>
<LinkButton
type="link"
onClick={() => updateMode('enter')}
>
Enter
</LinkButton>
</>
);
}

View File

@ -0,0 +1,4 @@
export * from './FinishContent';
export * from './ResetContent';
export * from './EnterContent';
export * from './RegisterContent'

View File

@ -4,9 +4,10 @@ import React, { FC, useState, useEffect } from 'react';
import { Button } from 'antd'; import { Button } from 'antd';
import { useSelectedLayoutSegment } from 'next/navigation'; import { useSelectedLayoutSegment } from 'next/navigation';
import { styled } from 'styled-components'; import { styled } from 'styled-components';
import { AuthModal } from '../../Modals/AuthModal';
import { checkAuthToken } from '../../../utils/storage/auth';
import { Link } from '../../../navigation'; import { Link } from '../../../navigation';
import { AUTH_TOKEN_KEY } from '../../../constants/common';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { AuthModal } from '../../Modals/AuthModal';
type HeaderAuthLinksProps = { type HeaderAuthLinksProps = {
enterTitle: string; enterTitle: string;
@ -35,6 +36,7 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
const [mode, setMode] = useState<'enter' | 'register' | 'reset' | 'finish'>('enter'); const [mode, setMode] = useState<'enter' | 'register' | 'reset' | 'finish'>('enter');
const selectedLayoutSegment = useSelectedLayoutSegment(); const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment || ''; const pathname = selectedLayoutSegment || '';
const [token, setToken] = useLocalStorage(AUTH_TOKEN_KEY, '');
useEffect(() => { useEffect(() => {
if (!isOpenModal) { if (!isOpenModal) {
@ -47,10 +49,10 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
setIsOpenModal(true); setIsOpenModal(true);
}; };
return checkAuthToken() return token
? ( ? (
<li> <li>
<Link href={'/account' as any} className={pathname === 'account' ? 'active' : ''}>{accountTitle}</Link> <Link href={'/account/sessions' as any} className={pathname === 'account' ? 'active' : ''}>{accountTitle}</Link>
</li> </li>
) )
: ( : (
@ -79,6 +81,7 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
handleCancel={() => setIsOpenModal(false)} handleCancel={() => setIsOpenModal(false)}
mode={mode} mode={mode}
updateMode={setMode} updateMode={setMode}
updateToken={setToken}
/> />
</> </>
); );

View File

@ -28,17 +28,13 @@ const Input = styled(AntdInput.Password)`
&.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless) { &.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless) {
border-color: #ff4d4f !important; border-color: #ff4d4f !important;
} }
.ant-input-suffix {
opacity: .3;
}
`; `;
export const CustomInputPassword = (props: any) => ( export const CustomInputPassword = (props: any) => (
<Input <Input
iconRender={(visible) => (visible iconRender={(visible) => (visible
? <EyeOutlined style={{ color: '#2C7873', fontSize: 20}} /> ? <EyeOutlined style={{ color: '#2C7873', fontSize: 20, opacity: .3 }} />
: <EyeInvisibleOutlined style={{ color: '#2C7873', fontSize: 20}} /> : <EyeInvisibleOutlined style={{ color: '#2C7873', fontSize: 20 }} />
)} )}
{...props} {...props}
/> />

View File

@ -0,0 +1,17 @@
import React from 'react';
import { styled } from 'styled-components';
import { Button as AntdButton } from 'antd';
const Button = styled(AntdButton)`
background: #66A5AD !important;
font-size: 15px !important;
border-radius: 8px !important;
height: 54px !important;
box-shadow: 0px 2px 4px 0px rgba(102, 165, 173, 0.32) !important;
`;
export const FilledButton = (props: any) => (
<Button {...props}>
{props.children}
</Button>
);

View File

@ -0,0 +1,16 @@
import React from 'react';
import { styled } from 'styled-components';
import { Button as AntdButton } from 'antd';
const Button = styled(AntdButton)`
color: #66A5AD !important;
font-size: 15px !important;
height: auto !important;
padding: 0 !important;
`;
export const LinkButton = (props: any) => (
<Button {...props}>
{props.children}
</Button>
);

View File

@ -0,0 +1,26 @@
import React from 'react';
import { styled } from 'styled-components';
import { Button as AntdButton } from 'antd';
const Button = styled(AntdButton)`
display: inline-flex !important;
justify-content: center;
align-items: center;
gap: 16px;
border-color: #66A5AD !important;
color: #66A5AD !important;
font-size: 15px !important;
border-radius: 8px !important;
height: 54px !important;
span {
margin-inline-end: 0 !important;
line-height: 15px !important;
}
`;
export const OutlinedButton = (props: any) => (
<Button {...props}>
{props.children}
</Button>
);

View File

@ -9,3 +9,6 @@ export * from './CustomInputPassword';
export * from './CustomSelect'; export * from './CustomSelect';
export * from './CustomMultiSelect'; export * from './CustomMultiSelect';
export * from './CustomSpin'; export * from './CustomSpin';
export * from './FilledButton';
export * from './OutlinedButton';
export * from './LinkButton';

View File

@ -0,0 +1,26 @@
import { useState, useEffect } from 'react';
function getStorageValue(key: string, defaultValue: any) {
if (typeof window !== 'undefined') {
const saved = localStorage.getItem(key);
return saved || defaultValue;
}
};
export function deleteStorageKey(key: string) {
if (typeof window !== 'undefined') {
localStorage.removeItem(key);
}
};
export const useLocalStorage = (key: string, defaultValue: any) => {
const [value, setValue] = useState(() => {
return getStorageValue(key, defaultValue);
});
useEffect(() => {
localStorage.setItem(key, value);
}, [key, value]);
return [value, setValue];
};

View File

@ -375,7 +375,7 @@ a {
padding: 8px 31px; padding: 8px 31px;
justify-content: center; justify-content: center;
align-items: center; align-items: center;
color: $white; color: $white !important;
@include rem(15); @include rem(15);
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
@ -526,7 +526,6 @@ a {
background: lightgray 50%; background: lightgray 50%;
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32); box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
overflow: hidden; overflow: hidden;
margin-right: -16px;
img { img {
object-fit: cover; object-fit: cover;
@ -606,7 +605,7 @@ a {
a { a {
text-decoration: none; text-decoration: none;
color: #FF8A00; color: #FF8A00 !important;
@include rem(15); @include rem(15);
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
@ -840,7 +839,7 @@ a {
justify-content: center; justify-content: center;
a { a {
color: #003B46; color: #003B46 !important;
@include rem(18); @include rem(18);
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;

View File

@ -97,6 +97,7 @@ textarea {
display: flex; display: flex;
gap: 16px; gap: 16px;
align-items: center; align-items: center;
margin-bottom: 4px;
&__edit { &__edit {
position: relative; position: relative;

View File

@ -1,6 +1,9 @@
@import "_variables"; @import "_variables";
.b-header { .b-header {
position: relative;
z-index: 2;
.b-inner { .b-inner {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
@ -68,7 +71,7 @@
text-decoration: none; text-decoration: none;
&.active { &.active {
color: #003B46; color: #003B46 !important;
} }
} }

View File

@ -269,16 +269,17 @@
border: 1px solid #003B46; border: 1px solid #003B46;
height: 40px; height: 40px;
padding: 8px 16px; padding: 8px 16px;
color: #003B46; color: #003B46 !important;
@include rem(16); @include rem(16);
font-style: normal; font-style: normal;
font-weight: 700; font-weight: 700;
line-height: 133.333%; line-height: 133.333%;
text-decoration: none; text-decoration: none;
transition: all .2s ease;
&:hover, &:hover,
&.active { &.active {
color: $white; color: $white !important;
background-color: #003B46; background-color: #003B46;
} }
} }
@ -753,7 +754,11 @@
border-bottom: none; border-bottom: none;
a { a {
color: #D93E5C; color: #D93E5C !important;
&:before {
background-image: none;
}
} }
} }
} }
@ -780,7 +785,7 @@
justify-content: space-between; justify-content: space-between;
height: 49px; height: 49px;
gap: 8px; gap: 8px;
color: #003B46; color: #003B46 !important;
@include rem(18); @include rem(18);
font-style: normal; font-style: normal;
font-weight: 600; font-weight: 600;
@ -804,7 +809,7 @@
&.active { &.active {
color: #66A5AD; color: #66A5AD !important;
padding-right: 0; padding-right: 0;
&:before { &:before {
@ -1016,17 +1021,8 @@
gap: 16px; gap: 16px;
} }
&__read {
color: #6FB98F;
@include rem(15);
font-style: normal;
font-weight: 500;
line-height: 150%;
text-decoration: none;
}
&__delete { &__delete {
color: #D93E5C; color: #D93E5C !important;
@include rem(15); @include rem(15);
font-style: normal; font-style: normal;
font-weight: 500; font-weight: 500;
@ -1046,9 +1042,11 @@
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
margin-bottom: 16px; margin-bottom: 16px;
padding: 12px 12px 0;
.card-profile { .card-profile {
cursor: pointer; cursor: pointer;
margin: 0 !important;
&__header { &__header {
gap: 8px; gap: 8px;
@ -1135,7 +1133,8 @@
margin-bottom: 24px; margin-bottom: 24px;
.card-profile { .card-profile {
border: none; border: none !important;
&__header { &__header {
gap: 8px; gap: 8px;
@ -1278,6 +1277,11 @@
font-weight: 300; font-weight: 300;
line-height: 116.667%; line-height: 116.667%;
} }
&__utc {
margin-top: 8px;
width: 150px;
}
} }
.schedule { .schedule {
@ -1318,9 +1322,9 @@
cursor: pointer; cursor: pointer;
border-radius: 8px; border-radius: 8px;
border: 1px solid #D93E5C; border: 1px solid #D93E5C;
display: flex;
width: 54px; width: 54px;
height: 54px; height: 54px;
padding: 0 27px;
background-image: url(/images/close.svg); background-image: url(/images/close.svg);
background-position: 50%; background-position: 50%;
background-repeat: no-repeat; background-repeat: no-repeat;

View File

@ -1,5 +1,5 @@
import { SearchData } from '../types/tags'; import { SearchData } from '../types/tags';
import { AdditionalFilter, Filter } from '../types/experts'; import { AdditionalFilter, Filter, GeneralFilter } from '../types/experts';
export const getDefaultFilter = (searchData: SearchData | null): Filter => { export const getDefaultFilter = (searchData: SearchData | null): Filter => {
const themesTagIds = searchData?.themesGroups?.reduce<number[]>((result, { tags }) => { const themesTagIds = searchData?.themesGroups?.reduce<number[]>((result, { tags }) => {
@ -101,3 +101,21 @@ export const getObjectByAdditionalFilter = (searchParams?: any): AdditionalFilte
return additionalFilter; return additionalFilter;
}; };
export const getSearchParamsString = (filter: GeneralFilter): string => {
const searchParams = new URLSearchParams();
Object.entries(filter).forEach(([key, value]) => {
if (value) {
if (Array.isArray(value)) {
value.forEach((val) => {
searchParams.append(key, `${val}`);
});
} else {
searchParams.set(key, `${value}`);
}
}
})
return searchParams.toString();
};

View File

@ -1,23 +0,0 @@
'use client';
import { AUTH_TOKEN_KEY } from '../../constants/common';
export function checkAuthToken() {
return !!getAuthToken();
}
export function getAuthToken() {
return '';
}
// export function getAuthToken() {
// return localStorage.getItem(AUTH_TOKEN_KEY);
// }
//
// export function removeAuthToken() {
// localStorage.removeItem(AUTH_TOKEN_KEY);
// }
//
export function setAuthToken(token: string) {
// localStorage.setItem(AUTH_TOKEN_KEY, token);
}