feat: add normal auth
This commit is contained in:
parent
9f225294c7
commit
bdd382042c
|
@ -68,7 +68,6 @@
|
|||
},
|
||||
"Notifications": {
|
||||
"title": "Notifications",
|
||||
"read": "Read",
|
||||
"delete": "Delete"
|
||||
},
|
||||
"Sessions": {
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
import { AxiosResponse } from 'axios';
|
||||
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(
|
||||
'/auth/login',
|
||||
{ login, password },
|
||||
data,
|
||||
{
|
||||
headers: {
|
||||
'X-User-Language': locale
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
export const getRegister = (locale: string): Promise<AxiosResponse<{ jwtToken: string }>> => (
|
||||
apiClient.post(
|
||||
'/auth/register',
|
||||
{},
|
||||
{
|
||||
headers: {
|
||||
'X-User-Language': locale
|
||||
|
|
|
@ -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}`
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
|
@ -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
|
||||
and suppliers.
|
||||
</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">
|
||||
We may collect, use, store and transfer different kinds of personal data about you which we have grouped
|
||||
together as follows:<br />
|
|
@ -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>
|
||||
);
|
||||
};
|
|
@ -1,28 +1,28 @@
|
|||
import React from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { Link } from '../../../../../../navigation';
|
||||
|
||||
export function generateStaticParams({
|
||||
params: { locale, userId },
|
||||
}: { params: { locale: string, userId: string } }) {
|
||||
const result: { locale: string, userId: string, textId: string }[] = [];
|
||||
params: { locale },
|
||||
}: { params: { locale: string } }) {
|
||||
const result: { locale: string, textId: string }[] = [];
|
||||
const chats = [{ textId: '1' }, { textId: '2' }, { textId: '3' }];
|
||||
|
||||
chats.forEach(({ textId }) => {
|
||||
result.push({ locale, userId, textId });
|
||||
result.push({ locale, textId });
|
||||
});
|
||||
|
||||
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');
|
||||
|
||||
return (
|
||||
<>
|
||||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item">
|
||||
<Link href={`/${params.userId}/messages` as any}>
|
||||
<Link href={'/account/messages' as any}>
|
||||
{t('title')}
|
||||
</Link>
|
||||
</li>
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react';
|
||||
import React, { Suspense } from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Link } from '../../../../navigation';
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { CustomInput } from '../../../../../components/view';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bbuddy - Account - Messages',
|
||||
description: 'Bbuddy desc messages'
|
||||
};
|
||||
|
||||
export default function Messages({ params }: { params: { userId: string } }) {
|
||||
export default function Messages() {
|
||||
const t = useTranslations('Account.Messages');
|
||||
|
||||
return (
|
||||
|
@ -16,10 +17,13 @@ export default function Messages({ params }: { params: { userId: string } }) {
|
|||
<ol className="breadcrumb">
|
||||
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
|
||||
</ol>
|
||||
<Suspense>
|
||||
<CustomInput placeholder="Name" />
|
||||
</Suspense>
|
||||
<div className="messages-session">
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={`/${params.userId}/messages/1` as any}
|
||||
href={'1' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
|
@ -41,7 +45,7 @@ export default function Messages({ params }: { params: { userId: string } }) {
|
|||
</Link>
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={`/${params.userId}/messages/2` as any}
|
||||
href={'2' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
||||
|
@ -60,7 +64,7 @@ export default function Messages({ params }: { params: { userId: string } }) {
|
|||
</Link>
|
||||
<Link
|
||||
className="card-profile"
|
||||
href={`/${params.userId}/messages/3` as any}
|
||||
href={'3' as any}
|
||||
>
|
||||
<div className="card-profile__header">
|
||||
<div className="card-profile__header__portrait">
|
|
@ -23,7 +23,6 @@ export default function Notifications() {
|
|||
</div>
|
||||
<div className="b-notifications__date">25 may 2022</div>
|
||||
<div className="b-notifications__inner">
|
||||
<a href="#" className="b-notifications__read">{t('read')}</a>
|
||||
<a href="#" className="b-notifications__delete">{t('delete')}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -34,7 +33,6 @@ export default function Notifications() {
|
|||
</div>
|
||||
<div className="b-notifications__date">25 may 2022</div>
|
||||
<div className="b-notifications__inner">
|
||||
<a href="#" className="b-notifications__read">{t('read')}</a>
|
||||
<a href="#" className="b-notifications__delete">{t('delete')}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -45,7 +43,6 @@ export default function Notifications() {
|
|||
</div>
|
||||
<div className="b-notifications__date">25 may 2022</div>
|
||||
<div className="b-notifications__inner">
|
||||
<a href="#" className="b-notifications__read">{t('read')}</a>
|
||||
<a href="#" className="b-notifications__delete">{t('delete')}</a>
|
||||
</div>
|
||||
</div>
|
|
@ -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>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { Link } from '../../../../../../navigation';
|
||||
|
||||
export default function ChangePassword({ params }: { params: { userId: string } }) {
|
||||
const t = useTranslations('Account.Settings');
|
|
@ -1,7 +1,8 @@
|
|||
import React from 'react';
|
||||
import React, {Suspense} from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import {Link} from "../../../../navigation";
|
||||
import { Link } from '../../../../../navigation';
|
||||
import { CustomInput } from '../../../../../components/view';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
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>
|
||||
<div className="form-field">
|
||||
<input type="text" placeholder={t('name')} className="base-input" id="" value="" />
|
||||
<CustomInput placeholder={t('name')} />
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<input type="text" placeholder={t('surname')} className="base-input" id="" value="" />
|
||||
<CustomInput placeholder={t('surname')} />
|
||||
</div>
|
||||
<div className="form-field date">
|
||||
<input className="base-input " type="text" placeholder={t('birthday')} id="" value="" />
|
||||
<CustomInput placeholder={t('birthday')} />
|
||||
</div>
|
||||
<div className="form-field">
|
||||
<input type="email" placeholder={t('email')} className="base-input" id="" value="" />
|
||||
<CustomInput type="email" placeholder={t('email')} />
|
||||
</div>
|
||||
<div className="form-link">
|
||||
<Link href={`/${params.userId}/settings/change-password` as any}>
|
||||
<Link href={'change-password' as any}>
|
||||
{t('change-password')}
|
||||
</Link>
|
||||
</div>
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import {useTranslations} from "next-intl";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bbuddy - Account - Help & Support',
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import type { Metadata } from 'next';
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useTranslations } from 'next-intl';
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: 'Bbuddy - Account - Work with us',
|
|
@ -1,64 +1,18 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import { Metadata } from 'next';
|
||||
import { notFound } from 'next/navigation';
|
||||
import { useTranslations } from 'next-intl';
|
||||
import { AccountMenu } from '../../../components/Account';
|
||||
'use client';
|
||||
|
||||
type AccountLayoutProps = {
|
||||
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 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>
|
||||
);
|
||||
import { ReactNode, useEffect } from 'react';
|
||||
import { redirect, notFound } from 'next/navigation';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
export default function AccountLayout({ children }: { children: ReactNode }) {
|
||||
const [token] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
useEffect(() => {
|
||||
if(!token){
|
||||
notFound();
|
||||
}
|
||||
}, []);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import { redirect, notFound } from 'next/navigation';
|
||||
// import { checkAuthToken } from '../../../utils/storage/auth';
|
||||
'use client';
|
||||
|
||||
export default function AccountMainPage() {
|
||||
// if (checkAuthToken()) {
|
||||
// redirect('/sessions');
|
||||
// } else {
|
||||
// notFound();
|
||||
// }
|
||||
import React from 'react';
|
||||
import { redirect } from 'next/navigation';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
|
||||
redirect('/sessions');
|
||||
export default function Account() {
|
||||
const [token] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
return token ? redirect('sessions') : null;
|
||||
};
|
||||
|
|
|
@ -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')
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
|
@ -1,18 +1,39 @@
|
|||
'use client';
|
||||
|
||||
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 { 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 pathname = selectedLayoutSegment || '';
|
||||
const paths = usePathname();
|
||||
|
||||
const onLogout = () => {
|
||||
deleteStorageKey(AUTH_TOKEN_KEY);
|
||||
window?.location?.replace(`/${paths.split('/')[1]}/`);
|
||||
};
|
||||
|
||||
return (
|
||||
<ul className="list-sidebar">
|
||||
{menu.map(({ path, title, count }) => (
|
||||
<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}
|
||||
{count ? (
|
||||
<span className="count">{count}</span>
|
||||
|
@ -21,7 +42,12 @@ export const AccountMenu = ({ userId, menu }: { userId: string, menu: { path: st
|
|||
</li>
|
||||
))}
|
||||
<li className="list-sidebar__item">
|
||||
<a href="#" className="">Log Out</a>
|
||||
<Logout
|
||||
type="link"
|
||||
onClick={onLogout}
|
||||
>
|
||||
Log Out
|
||||
</Logout>
|
||||
</li>
|
||||
</ul>
|
||||
)
|
||||
|
|
|
@ -1,19 +1,35 @@
|
|||
'use client';
|
||||
|
||||
import React from 'react';
|
||||
import { Tabs } from 'antd';
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { CustomSelect } from '../view';
|
||||
|
||||
const Tab = styled.div``;
|
||||
|
||||
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 = () => (
|
||||
<>
|
||||
<div className="filter-session">
|
||||
<div className="filter-session__item filter-session__item--type">
|
||||
<select name="" id="">
|
||||
<option value="">1</option>
|
||||
<option value="">2</option>
|
||||
</select>
|
||||
<div className="filter-session__item">
|
||||
<CustomSelect
|
||||
label="Topic"
|
||||
value={sort}
|
||||
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 className="list-session">
|
||||
<div className="card-profile">
|
||||
|
@ -72,39 +88,48 @@ export const SessionsTabs = ({ intlConfig }: { intlConfig: Record<string, string
|
|||
{
|
||||
key: 'upcoming',
|
||||
label: (
|
||||
<div className="tabs-session__item active">
|
||||
<>
|
||||
{intlConfig?.upcoming || 'Tab 1'}
|
||||
<span className="count">3</span>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
children: getChildren()
|
||||
},
|
||||
{
|
||||
key: 'requested',
|
||||
label: (
|
||||
<div className="tabs-session__item">
|
||||
<>
|
||||
{intlConfig?.requested || 'Tab 2'}
|
||||
<span className="count">2</span>
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
children: getChildren()
|
||||
},
|
||||
{
|
||||
key: 'recent',
|
||||
label: (
|
||||
<div className="tabs-session__item">
|
||||
<>
|
||||
{intlConfig?.recent || 'Tab 3'}
|
||||
</div>
|
||||
</>
|
||||
),
|
||||
children: getChildren()
|
||||
}
|
||||
];
|
||||
|
||||
return (
|
||||
<Tabs
|
||||
defaultActiveKey="upcoming"
|
||||
items={tabs}
|
||||
renderTabBar={(props, DefaultTabBar) => (<DefaultTabBar {...props} className="tabs-session" />)}
|
||||
/>
|
||||
<>
|
||||
<div className="tabs-session">
|
||||
{tabs.map((tab, index) => (
|
||||
<Tab
|
||||
key={index}
|
||||
className={`tabs-session__item ${index === activeTab ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab(index)}
|
||||
>
|
||||
{tab.label}
|
||||
</Tab>
|
||||
))}
|
||||
</div>
|
||||
{tabs[activeTab].children}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -47,7 +47,7 @@ export const ExpertsList = ({
|
|||
} else {
|
||||
setExperts(data);
|
||||
}
|
||||
}, []);
|
||||
}, [searchParams]);
|
||||
|
||||
return experts ? (
|
||||
<>
|
||||
|
|
|
@ -2,11 +2,11 @@
|
|||
|
||||
import React, { useCallback, useState } from 'react';
|
||||
import { Button, List } from 'antd';
|
||||
import { useSearchParams } from 'next/navigation';
|
||||
import { Filter } from '../../types/experts';
|
||||
import { SearchData, Tag } from '../../types/tags';
|
||||
import { useSearchParams, usePathname } from 'next/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';
|
||||
|
||||
type ExpertsFilterProps = {
|
||||
|
@ -26,6 +26,7 @@ export const ExpertsFilter = ({
|
|||
}: ExpertsFilterProps) => {
|
||||
const searchParams = useSearchParams();
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
const [filter, setFilter] = useState<Filter | undefined>(getObjectByFilter(searchParams));
|
||||
|
||||
const onChangeTags = useCallback((id: number, checked: boolean) => {
|
||||
|
@ -82,6 +83,14 @@ export const ExpertsFilter = ({
|
|||
}, [filter, searchParams, searchData]);
|
||||
|
||||
const goToFilterPage = useCallback(() => {
|
||||
// const newFilter: GeneralFilter = {
|
||||
// ...filter,
|
||||
// ...getObjectByAdditionalFilter(searchParams)
|
||||
// };
|
||||
// const search = getSearchParamsString(newFilter);
|
||||
//
|
||||
// router.push(search ? `${pathname}?${search}` : pathname, { scroll: false });
|
||||
|
||||
router.push({
|
||||
pathname: basePath as any,
|
||||
query: {
|
||||
|
|
|
@ -1,21 +1,19 @@
|
|||
'use client';
|
||||
|
||||
import React, { FC, useState } from 'react';
|
||||
import React, { Dispatch, FC, SetStateAction, useEffect } from 'react';
|
||||
import { usePathname } from 'next/navigation';
|
||||
import Image from 'next/image';
|
||||
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 { styled } from 'styled-components';
|
||||
import { CustomInput, CustomInputPassword } from '../view';
|
||||
import { getAuth } from '../../actions/auth';
|
||||
import { setAuthToken } from '../../utils/storage/auth';
|
||||
import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent';
|
||||
|
||||
type AuthModalProps = {
|
||||
open: boolean;
|
||||
handleCancel: () => void;
|
||||
mode: 'enter' | 'register' | 'reset' | 'finish';
|
||||
updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void;
|
||||
updateToken: string | Dispatch<SetStateAction<string | undefined>> | undefined;
|
||||
};
|
||||
|
||||
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> = ({
|
||||
open,
|
||||
handleCancel,
|
||||
mode,
|
||||
updateMode
|
||||
updateMode,
|
||||
updateToken
|
||||
}) => {
|
||||
const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>();
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
const paths = usePathname().split('/');
|
||||
|
||||
const onAfterClose = () => {
|
||||
form.resetFields();
|
||||
};
|
||||
|
||||
const onSubmit = () => {
|
||||
form.validateFields().then(() => {
|
||||
const { login, password } = form.getFieldsValue();
|
||||
setIsLoading(true);
|
||||
getAuth(login, password, paths[1])
|
||||
.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 = () => {
|
||||
useEffect(() => {
|
||||
if (form) {
|
||||
form.resetFields();
|
||||
}
|
||||
}, [mode]);
|
||||
|
||||
const onUpdateToken = (token: string) => {
|
||||
if (updateToken && typeof updateToken !== 'string') {
|
||||
updateToken(token);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
open={open}
|
||||
title={undefined}
|
||||
onOk={onSubmit}
|
||||
onOk={undefined}
|
||||
onCancel={handleCancel}
|
||||
afterClose={onAfterClose}
|
||||
footer={false}
|
||||
|
@ -126,124 +77,28 @@ export const AuthModal: FC<AuthModalProps> = ({
|
|||
<img className="" src="/images/logo-auth.svg" alt=""/>
|
||||
</div>
|
||||
{mode === 'enter' && (
|
||||
<>
|
||||
<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"
|
||||
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>
|
||||
</>
|
||||
<EnterContent
|
||||
form={form}
|
||||
updateMode={updateMode}
|
||||
locale={paths[1]}
|
||||
handleCancel={handleCancel}
|
||||
updateToken={onUpdateToken}
|
||||
/>
|
||||
)}
|
||||
{mode === 'register' && (
|
||||
<>
|
||||
<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.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>
|
||||
</>
|
||||
<RegisterContent
|
||||
form={form}
|
||||
locale={paths[1]}
|
||||
updateMode={updateMode}
|
||||
updateToken={onUpdateToken}
|
||||
handleCancel={handleCancel}
|
||||
/>
|
||||
)}
|
||||
{mode === 'reset' && (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
<ResetContent form={form} updateMode={updateMode} />
|
||||
)}
|
||||
{mode === 'finish' && (
|
||||
<>
|
||||
<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>
|
||||
</>
|
||||
<FinishContent />
|
||||
)}
|
||||
<div className="b-modal__auth__agreement">
|
||||
I have read and agree with the terms of the
|
||||
|
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
</>
|
||||
);
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
};
|
|
@ -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>
|
||||
</>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
export * from './FinishContent';
|
||||
export * from './ResetContent';
|
||||
export * from './EnterContent';
|
||||
export * from './RegisterContent'
|
|
@ -4,9 +4,10 @@ import React, { FC, useState, useEffect } from 'react';
|
|||
import { Button } from 'antd';
|
||||
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||
import { styled } from 'styled-components';
|
||||
import { AuthModal } from '../../Modals/AuthModal';
|
||||
import { checkAuthToken } from '../../../utils/storage/auth';
|
||||
import { Link } from '../../../navigation';
|
||||
import { AUTH_TOKEN_KEY } from '../../../constants/common';
|
||||
import { useLocalStorage } from '../../../hooks/useLocalStorage';
|
||||
import { AuthModal } from '../../Modals/AuthModal';
|
||||
|
||||
type HeaderAuthLinksProps = {
|
||||
enterTitle: string;
|
||||
|
@ -35,6 +36,7 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
|
|||
const [mode, setMode] = useState<'enter' | 'register' | 'reset' | 'finish'>('enter');
|
||||
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||
const pathname = selectedLayoutSegment || '';
|
||||
const [token, setToken] = useLocalStorage(AUTH_TOKEN_KEY, '');
|
||||
|
||||
useEffect(() => {
|
||||
if (!isOpenModal) {
|
||||
|
@ -47,10 +49,10 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
|
|||
setIsOpenModal(true);
|
||||
};
|
||||
|
||||
return checkAuthToken()
|
||||
return token
|
||||
? (
|
||||
<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>
|
||||
)
|
||||
: (
|
||||
|
@ -79,6 +81,7 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
|
|||
handleCancel={() => setIsOpenModal(false)}
|
||||
mode={mode}
|
||||
updateMode={setMode}
|
||||
updateToken={setToken}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -28,17 +28,13 @@ const Input = styled(AntdInput.Password)`
|
|||
&.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless) {
|
||||
border-color: #ff4d4f !important;
|
||||
}
|
||||
|
||||
.ant-input-suffix {
|
||||
opacity: .3;
|
||||
}
|
||||
`;
|
||||
|
||||
export const CustomInputPassword = (props: any) => (
|
||||
<Input
|
||||
iconRender={(visible) => (visible
|
||||
? <EyeOutlined style={{ color: '#2C7873', fontSize: 20}} />
|
||||
: <EyeInvisibleOutlined style={{ color: '#2C7873', fontSize: 20}} />
|
||||
? <EyeOutlined style={{ color: '#2C7873', fontSize: 20, opacity: .3 }} />
|
||||
: <EyeInvisibleOutlined style={{ color: '#2C7873', fontSize: 20 }} />
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
|
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -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>
|
||||
);
|
|
@ -9,3 +9,6 @@ export * from './CustomInputPassword';
|
|||
export * from './CustomSelect';
|
||||
export * from './CustomMultiSelect';
|
||||
export * from './CustomSpin';
|
||||
export * from './FilledButton';
|
||||
export * from './OutlinedButton';
|
||||
export * from './LinkButton';
|
||||
|
|
|
@ -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];
|
||||
};
|
|
@ -375,7 +375,7 @@ a {
|
|||
padding: 8px 31px;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: $white;
|
||||
color: $white !important;
|
||||
@include rem(15);
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
@ -526,7 +526,6 @@ a {
|
|||
background: lightgray 50%;
|
||||
box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32);
|
||||
overflow: hidden;
|
||||
margin-right: -16px;
|
||||
|
||||
img {
|
||||
object-fit: cover;
|
||||
|
@ -606,7 +605,7 @@ a {
|
|||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: #FF8A00;
|
||||
color: #FF8A00 !important;
|
||||
@include rem(15);
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
|
@ -840,7 +839,7 @@ a {
|
|||
justify-content: center;
|
||||
|
||||
a {
|
||||
color: #003B46;
|
||||
color: #003B46 !important;
|
||||
@include rem(18);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
|
|
|
@ -97,6 +97,7 @@ textarea {
|
|||
display: flex;
|
||||
gap: 16px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
|
||||
&__edit {
|
||||
position: relative;
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
@import "_variables";
|
||||
|
||||
.b-header {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
|
||||
.b-inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
@ -68,7 +71,7 @@
|
|||
text-decoration: none;
|
||||
|
||||
&.active {
|
||||
color: #003B46;
|
||||
color: #003B46 !important;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -269,16 +269,17 @@
|
|||
border: 1px solid #003B46;
|
||||
height: 40px;
|
||||
padding: 8px 16px;
|
||||
color: #003B46;
|
||||
color: #003B46 !important;
|
||||
@include rem(16);
|
||||
font-style: normal;
|
||||
font-weight: 700;
|
||||
line-height: 133.333%;
|
||||
text-decoration: none;
|
||||
transition: all .2s ease;
|
||||
|
||||
&:hover,
|
||||
&.active {
|
||||
color: $white;
|
||||
color: $white !important;
|
||||
background-color: #003B46;
|
||||
}
|
||||
}
|
||||
|
@ -753,7 +754,11 @@
|
|||
border-bottom: none;
|
||||
|
||||
a {
|
||||
color: #D93E5C;
|
||||
color: #D93E5C !important;
|
||||
|
||||
&:before {
|
||||
background-image: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -780,7 +785,7 @@
|
|||
justify-content: space-between;
|
||||
height: 49px;
|
||||
gap: 8px;
|
||||
color: #003B46;
|
||||
color: #003B46 !important;
|
||||
@include rem(18);
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
|
@ -804,7 +809,7 @@
|
|||
|
||||
|
||||
&.active {
|
||||
color: #66A5AD;
|
||||
color: #66A5AD !important;
|
||||
padding-right: 0;
|
||||
|
||||
&:before {
|
||||
|
@ -1016,17 +1021,8 @@
|
|||
gap: 16px;
|
||||
}
|
||||
|
||||
&__read {
|
||||
color: #6FB98F;
|
||||
@include rem(15);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
line-height: 150%;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&__delete {
|
||||
color: #D93E5C;
|
||||
color: #D93E5C !important;
|
||||
@include rem(15);
|
||||
font-style: normal;
|
||||
font-weight: 500;
|
||||
|
@ -1046,9 +1042,11 @@
|
|||
flex-direction: column;
|
||||
gap: 16px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px 12px 0;
|
||||
|
||||
.card-profile {
|
||||
cursor: pointer;
|
||||
margin: 0 !important;
|
||||
|
||||
&__header {
|
||||
gap: 8px;
|
||||
|
@ -1135,7 +1133,8 @@
|
|||
margin-bottom: 24px;
|
||||
|
||||
.card-profile {
|
||||
border: none;
|
||||
border: none !important;
|
||||
|
||||
&__header {
|
||||
gap: 8px;
|
||||
|
||||
|
@ -1278,6 +1277,11 @@
|
|||
font-weight: 300;
|
||||
line-height: 116.667%;
|
||||
}
|
||||
|
||||
&__utc {
|
||||
margin-top: 8px;
|
||||
width: 150px;
|
||||
}
|
||||
}
|
||||
|
||||
.schedule {
|
||||
|
@ -1318,9 +1322,9 @@
|
|||
cursor: pointer;
|
||||
border-radius: 8px;
|
||||
border: 1px solid #D93E5C;
|
||||
display: flex;
|
||||
width: 54px;
|
||||
height: 54px;
|
||||
padding: 0 27px;
|
||||
background-image: url(/images/close.svg);
|
||||
background-position: 50%;
|
||||
background-repeat: no-repeat;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 => {
|
||||
const themesTagIds = searchData?.themesGroups?.reduce<number[]>((result, { tags }) => {
|
||||
|
@ -101,3 +101,21 @@ export const getObjectByAdditionalFilter = (searchParams?: any): AdditionalFilte
|
|||
|
||||
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();
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
Loading…
Reference in New Issue