feat: add normal auth
This commit is contained in:
parent
9f225294c7
commit
bdd382042c
|
@ -68,7 +68,6 @@
|
||||||
},
|
},
|
||||||
"Notifications": {
|
"Notifications": {
|
||||||
"title": "Notifications",
|
"title": "Notifications",
|
||||||
"read": "Read",
|
|
||||||
"delete": "Delete"
|
"delete": "Delete"
|
||||||
},
|
},
|
||||||
"Sessions": {
|
"Sessions": {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
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 />
|
|
@ -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 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>
|
|
@ -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">
|
|
@ -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>
|
|
@ -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 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');
|
|
@ -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>
|
|
@ -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',
|
|
@ -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 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',
|
|
@ -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>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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';
|
'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>
|
||||||
)
|
)
|
||||||
|
|
|
@ -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}
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -47,7 +47,7 @@ export const ExpertsList = ({
|
||||||
} else {
|
} else {
|
||||||
setExperts(data);
|
setExperts(data);
|
||||||
}
|
}
|
||||||
}, []);
|
}, [searchParams]);
|
||||||
|
|
||||||
return experts ? (
|
return experts ? (
|
||||||
<>
|
<>
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 { 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}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -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 './CustomSelect';
|
||||||
export * from './CustomMultiSelect';
|
export * from './CustomMultiSelect';
|
||||||
export * from './CustomSpin';
|
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;
|
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;
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
||||||
|
};
|
||||||
|
|
|
@ -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