feat: add custom form fields, fix auth modal
This commit is contained in:
parent
c94c69202e
commit
9f225294c7
|
@ -12,6 +12,7 @@
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
"antd": "^5.12.1",
|
"antd": "^5.12.1",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"next-intl": "^3.3.1",
|
"next-intl": "^3.3.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
@ -22,9 +23,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/eslint-plugin-next": "^14.0.4",
|
"@next/eslint-plugin-next": "^14.0.4",
|
||||||
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-next": "^14.0.3",
|
"eslint-config-next": "^14.0.3",
|
||||||
|
@ -640,6 +643,12 @@
|
||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.14.202",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||||
|
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"node_modules/@types/node": {
|
"node_modules/@types/node": {
|
||||||
"version": "20.9.4",
|
"version": "20.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
|
||||||
|
@ -675,6 +684,15 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/react-slick": {
|
||||||
|
"version": "0.23.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.13.tgz",
|
||||||
|
"integrity": "sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/scheduler": {
|
"node_modules/@types/scheduler": {
|
||||||
"version": "0.16.8",
|
"version": "0.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||||
|
@ -3209,6 +3227,11 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
"node_modules/lodash.debounce": {
|
"node_modules/lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
@ -5705,6 +5728,12 @@
|
||||||
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
"integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"@types/lodash": {
|
||||||
|
"version": "4.14.202",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.202.tgz",
|
||||||
|
"integrity": "sha512-OvlIYQK9tNneDlS0VN54LLd5uiPCBOp7gS5Z0f1mjoJYBrtStzgmJBxONW3U6OZqdtNzZPmn9BS/7WI7BFFcFQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
"@types/node": {
|
"@types/node": {
|
||||||
"version": "20.9.4",
|
"version": "20.9.4",
|
||||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
|
||||||
|
@ -5740,6 +5769,15 @@
|
||||||
"@types/react": "*"
|
"@types/react": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"@types/react-slick": {
|
||||||
|
"version": "0.23.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.13.tgz",
|
||||||
|
"integrity": "sha512-bNZfDhe/L8t5OQzIyhrRhBr/61pfBcWaYJoq6UDqFtv5LMwfg4NsVDD2J8N01JqdAdxLjOt66OZEp6PX+dGs/A==",
|
||||||
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"@types/react": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"@types/scheduler": {
|
"@types/scheduler": {
|
||||||
"version": "0.16.8",
|
"version": "0.16.8",
|
||||||
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz",
|
||||||
|
@ -7606,6 +7644,11 @@
|
||||||
"p-locate": "^5.0.0"
|
"p-locate": "^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
|
||||||
|
},
|
||||||
"lodash.debounce": {
|
"lodash.debounce": {
|
||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
"@ant-design/icons": "^5.2.6",
|
"@ant-design/icons": "^5.2.6",
|
||||||
"antd": "^5.12.1",
|
"antd": "^5.12.1",
|
||||||
"axios": "^1.6.5",
|
"axios": "^1.6.5",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"next": "14.0.3",
|
"next": "14.0.3",
|
||||||
"next-intl": "^3.3.1",
|
"next-intl": "^3.3.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
|
@ -23,9 +24,11 @@
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@next/eslint-plugin-next": "^14.0.4",
|
"@next/eslint-plugin-next": "^14.0.4",
|
||||||
|
"@types/lodash": "^4.14.202",
|
||||||
"@types/node": "^20",
|
"@types/node": "^20",
|
||||||
"@types/react": "^18",
|
"@types/react": "^18",
|
||||||
"@types/react-dom": "^18",
|
"@types/react-dom": "^18",
|
||||||
|
"@types/react-slick": "^0.23.13",
|
||||||
"autoprefixer": "^10.0.1",
|
"autoprefixer": "^10.0.1",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^8.55.0",
|
||||||
"eslint-config-next": "^14.0.3",
|
"eslint-config-next": "^14.0.3",
|
||||||
|
|
|
@ -1,16 +1,9 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { useTranslations } from 'next-intl';
|
||||||
import { getFilter } from '../../../../utils/filter';
|
|
||||||
import { getExpertsList } from '../../../../actions/experts';
|
|
||||||
import { getTagList } from '../../../../actions/tags';
|
|
||||||
import { Experts } from '../../../../components/Experts/Experts';
|
import { Experts } from '../../../../components/Experts/Experts';
|
||||||
|
|
||||||
export default async function ExpertsPage({ params, searchParams }: { params: { locale: string }, searchParams: { [key: string]: string | string[] | undefined } }) {
|
export default function ExpertsPage({ params }: { params: { locale: string } }) {
|
||||||
const searchData = await getTagList(params.locale);
|
const t = useTranslations('Experts');
|
||||||
const filter = getFilter(searchData, searchParams);
|
|
||||||
console.log('filter main page', filter);
|
|
||||||
const experts = await getExpertsList(filter, params.locale);
|
|
||||||
const t = await getTranslations('Experts');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-find">
|
<div className="main-find">
|
||||||
|
@ -21,10 +14,7 @@ export default async function ExpertsPage({ params, searchParams }: { params: {
|
||||||
<img src="/images/options-outline.svg" className="" alt=""/>
|
<img src="/images/options-outline.svg" className="" alt=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Experts
|
<Experts locale={params.locale} />
|
||||||
experts={experts}
|
|
||||||
searchData={searchData}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,16 +3,16 @@ import type { Metadata } from 'next';
|
||||||
import { notFound } from 'next/navigation';
|
import { notFound } from 'next/navigation';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Bbuddy - News item',
|
title: 'Bbuddy - Blog item',
|
||||||
description: 'Bbuddy desc news item'
|
description: 'Bbuddy desc blog item'
|
||||||
};
|
};
|
||||||
|
|
||||||
export function generateStaticParams() {
|
export function generateStaticParams() {
|
||||||
return [{ newsId: 'news-1' }, { newsId: 'news-2' }];
|
return [{ blogId: 'news-1' }, { blogId: 'news-2' }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function NewsItem({ params }: { params: { newsId: string } }) {
|
export default function BlogItem({ params }: { params: { blogId: string } }) {
|
||||||
if (!params?.newsId) notFound();
|
if (!params?.blogId) notFound();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="b-news-page">
|
<div className="b-news-page">
|
||||||
|
@ -20,7 +20,7 @@ export default function NewsItem({ params }: { params: { newsId: string } }) {
|
||||||
<h1 className="b-news-page__title">6 learnings from Shivpuri to Silicon Valley</h1>
|
<h1 className="b-news-page__title">6 learnings from Shivpuri to Silicon Valley</h1>
|
||||||
<div className="news-item__badge">Leadership & Management</div>
|
<div className="news-item__badge">Leadership & Management</div>
|
||||||
<div className="b-news-page__text">
|
<div className="b-news-page__text">
|
||||||
{`news id ${params.newsId}`}<br />
|
{`news id ${params.blogId}`}<br />
|
||||||
I’m excited to kick off this series of newsletters where I’ll be sharing my experiences, learnings,
|
I’m excited to kick off this series of newsletters where I’ll be sharing my experiences, learnings,
|
||||||
and best practices which helped me to grow both in my personal and professional life. My hope is to
|
and best practices which helped me to grow both in my personal and professional life. My hope is to
|
||||||
give back to the community and help anyone connect directly with me who may have got impacted with
|
give back to the community and help anyone connect directly with me who may have got impacted with
|
|
@ -2,11 +2,11 @@ import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Bbuddy - News',
|
title: 'Bbuddy - Blog',
|
||||||
description: 'Bbuddy desc news'
|
description: 'Bbuddy desc blog'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function News() {
|
export default function Blog() {
|
||||||
return (
|
return (
|
||||||
<div className="b-news">
|
<div className="b-news">
|
||||||
<div className="b-news__header">
|
<div className="b-news__header">
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { getTranslations } from 'next-intl/server';
|
import { useTranslations } from 'next-intl';
|
||||||
import { getFilter } from '../../../utils/filter';
|
|
||||||
import { getExpertsList } from '../../../actions/experts';
|
|
||||||
import { getTagList } from '../../../actions/tags';
|
|
||||||
import { Experts } from '../../../components/Experts/Experts';
|
import { Experts } from '../../../components/Experts/Experts';
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
@ -11,12 +8,8 @@ export const metadata: Metadata = {
|
||||||
description: 'Bbuddy desc experts'
|
description: 'Bbuddy desc experts'
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function ExpertsPage({ params, searchParams }: { params: { locale: string }, searchParams: { [key: string]: string | string[] | undefined } }) {
|
export default function ExpertsPage({ params }: { params: { locale: string } }) {
|
||||||
const searchData = await getTagList(params.locale);
|
const t = useTranslations('Experts');
|
||||||
const filter = getFilter(searchData, searchParams);
|
|
||||||
console.log('filter experts page', filter);
|
|
||||||
const experts = await getExpertsList(filter, params.locale);
|
|
||||||
const t = await getTranslations('Experts');
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="page-search">
|
<div className="page-search">
|
||||||
|
@ -29,8 +22,8 @@ export default async function ExpertsPage({ params, searchParams }: { params: {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Experts
|
<Experts
|
||||||
experts={experts}
|
locale={params.locale}
|
||||||
searchData={searchData}
|
basePath="/experts"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React, { useCallback, useState } from 'react';
|
import React, { useCallback, useState } from 'react';
|
||||||
import {Button, Select} from 'antd';
|
import { Button } from 'antd';
|
||||||
import { useSearchParams } from 'next/navigation';
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { useRouter } from '../../navigation';
|
import { useRouter } from '../../navigation';
|
||||||
import { AdditionalFilter } from '../../types/experts';
|
import { AdditionalFilter } from '../../types/experts';
|
||||||
import { LOCALES } from '../../constants/locale';
|
import { LOCALES } from '../../constants/locale';
|
||||||
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
|
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
|
||||||
import { CustomInput } from '../view';
|
import { CustomInput, CustomSelect, CustomMultiSelect } from '../view';
|
||||||
|
|
||||||
type ExpertAdditionalFilterProps = {
|
type ExpertAdditionalFilterProps = {
|
||||||
searchPlaceholder: string;
|
searchPlaceholder: string;
|
||||||
sortLabel: string;
|
sortLabel: string;
|
||||||
langLabel: string;
|
langLabel: string;
|
||||||
buttonFind: string;
|
buttonFind: string;
|
||||||
basePath?: string;
|
basePath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpertsAdditionalFilter = ({
|
export const ExpertsAdditionalFilter = ({
|
||||||
|
@ -22,28 +22,61 @@ export const ExpertsAdditionalFilter = ({
|
||||||
sortLabel,
|
sortLabel,
|
||||||
langLabel,
|
langLabel,
|
||||||
buttonFind,
|
buttonFind,
|
||||||
basePath = '/',
|
basePath,
|
||||||
}): ExpertAdditionalFilterProps => {
|
}: ExpertAdditionalFilterProps) => {
|
||||||
const searchParams = useSearchParams();
|
const searchParams = useSearchParams();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [filter, setFilter] = useState<AdditionalFilter | undefined>(getObjectByAdditionalFilter(searchParams));
|
const [filter, setFilter] = useState<AdditionalFilter | undefined>(getObjectByAdditionalFilter(searchParams));
|
||||||
|
|
||||||
const onChangeFilter = useCallback((key: string, value: any) => {
|
const onChangeInput = useCallback((e: any) => {
|
||||||
// setFilter({
|
if (e?.target?.value) {
|
||||||
// ...filter,
|
setFilter({
|
||||||
// [key]: value
|
...filter,
|
||||||
// })
|
text: e.target.value
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (filter?.text) {
|
||||||
|
const newFilter = { ...filter };
|
||||||
|
delete newFilter.text;
|
||||||
|
|
||||||
|
setFilter(newFilter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
const onChangeSort = useCallback((value: string) => {
|
||||||
|
const newFilter: AdditionalFilter = { ...filter };
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
newFilter.sort = value;
|
||||||
|
} else {
|
||||||
|
delete newFilter?.sort;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(newFilter);
|
||||||
|
}, [filter]);
|
||||||
|
|
||||||
|
const onChangeLang = useCallback((value: string[]) => {
|
||||||
|
const newFilter: AdditionalFilter = { ...filter };
|
||||||
|
|
||||||
|
if (value.length > 0) {
|
||||||
|
newFilter.language = value;
|
||||||
|
} else {
|
||||||
|
delete newFilter?.language;
|
||||||
|
}
|
||||||
|
|
||||||
|
setFilter(newFilter);
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
const goToFilterPage = useCallback(() => {
|
const goToFilterPage = useCallback(() => {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: basePath,
|
pathname: basePath as any,
|
||||||
query: {
|
query: {
|
||||||
...getObjectByFilter(searchParams),
|
...getObjectByFilter(searchParams),
|
||||||
...filter
|
...filter
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}, [filter, searchParams]);
|
}, [filter, searchParams, router]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="main-find__search">
|
<div className="main-find__search">
|
||||||
|
@ -51,13 +84,14 @@ export const ExpertsAdditionalFilter = ({
|
||||||
<CustomInput
|
<CustomInput
|
||||||
placeholder={searchPlaceholder}
|
placeholder={searchPlaceholder}
|
||||||
defaultValue={filter?.text}
|
defaultValue={filter?.text}
|
||||||
onChange={(e: any) => onChangeFilter('text', e?.target?.value)}
|
onChange={onChangeInput}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="main-find__search__sort">
|
<div className="main-find__search__sort">
|
||||||
<Select
|
<CustomSelect
|
||||||
defaultValue={filter?.sort}
|
label={sortLabel}
|
||||||
onChange={(val) => onChangeFilter('sort', val)}
|
value={filter?.sort}
|
||||||
|
onChange={onChangeSort}
|
||||||
options={[
|
options={[
|
||||||
{ value: 'byTop', label: 'By top views' },
|
{ value: 'byTop', label: 'By top views' },
|
||||||
{ value: 'byPriceAsc', label: 'By price ascending' },
|
{ value: 'byPriceAsc', label: 'By price ascending' },
|
||||||
|
@ -67,10 +101,10 @@ export const ExpertsAdditionalFilter = ({
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="main-find__search__language">
|
<div className="main-find__search__language">
|
||||||
<Select
|
<CustomMultiSelect
|
||||||
mode="multiple"
|
label={langLabel}
|
||||||
defaultValue={filter?.language}
|
value={filter?.language}
|
||||||
onChange={(val) => onChangeFilter('language', val)}
|
onChange={onChangeLang}
|
||||||
options={Object.entries(LOCALES).map(([ value, label ]) => ({ value, label }))}
|
options={Object.entries(LOCALES).map(([ value, label ]) => ({ value, label }))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,28 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useTranslations } from 'next-intl';
|
import { getTranslations } from 'next-intl/server';
|
||||||
import { SearchData } from '../../types/tags';
|
import { getTagList } from '../../actions/tags';
|
||||||
import { ExpertsData } from '../../types/experts';
|
import { getFilter } from '../../utils/filter';
|
||||||
|
import { getExpertsList } from '../../actions/experts';
|
||||||
import { ExpertsFilter } from './Filter';
|
import { ExpertsFilter } from './Filter';
|
||||||
import { ExpertsAdditionalFilter } from './AdditionalFilter';
|
import { ExpertsAdditionalFilter } from './AdditionalFilter';
|
||||||
import { ExpertsList } from './ExpertsList';
|
import { ExpertsList } from './ExpertsList';
|
||||||
import { CustomPagination } from '../view/CustomPagination';
|
|
||||||
|
|
||||||
type ExpertsProps = {
|
type ExpertsProps = {
|
||||||
searchData?: SearchData;
|
basePath?: string;
|
||||||
experts?: ExpertsData;
|
locale: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const Experts = ({ searchData, experts }: ExpertsProps) => {
|
export const Experts = async ({ basePath = '/', locale }: ExpertsProps) => {
|
||||||
const t = useTranslations('Experts');
|
const t = await getTranslations('Experts');
|
||||||
|
const searchData = await getTagList(locale);
|
||||||
|
const filter = getFilter(searchData);
|
||||||
|
const experts = await getExpertsList(filter, locale);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="row">
|
<div className="row">
|
||||||
<div className="col-xl-3 col-lg-4 d-none d-lg-block">
|
<div className="col-xl-3 col-lg-4 d-none d-lg-block">
|
||||||
<ExpertsFilter
|
<ExpertsFilter
|
||||||
searchData={searchData}
|
searchData={searchData}
|
||||||
basePath="/experts"
|
basePath={basePath}
|
||||||
priceTitle={t('filter.price', { from: searchData.sessionCostMin, to: searchData.sessionCostMax })}
|
priceTitle={t('filter.price', { from: searchData?.sessionCostMin || 0, to: searchData?.sessionCostMax || 0 })}
|
||||||
durationTitle={t('filter.duration', { from: searchData.sessionDurationMin, to: searchData.sessionDurationMax })}
|
durationTitle={t('filter.duration', { from: searchData?.sessionDurationMin || 0, to: searchData?.sessionDurationMax || 0 })}
|
||||||
buttonApply={t('filter.apply')}
|
buttonApply={t('filter.apply')}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -32,15 +35,16 @@ export const Experts = ({ searchData, experts }: ExpertsProps) => {
|
||||||
sortLabel={t('filter.sort')}
|
sortLabel={t('filter.sort')}
|
||||||
langLabel={t('filter.language')}
|
langLabel={t('filter.language')}
|
||||||
buttonFind={t('filter.find')}
|
buttonFind={t('filter.find')}
|
||||||
basePath="/experts"
|
basePath={basePath}
|
||||||
/>
|
/>
|
||||||
<ExpertsList
|
<ExpertsList
|
||||||
|
locale={locale}
|
||||||
data={experts}
|
data={experts}
|
||||||
|
baseFilter={filter}
|
||||||
priceTitle={t('list.price')}
|
priceTitle={t('list.price')}
|
||||||
durationTitle={t('list.duration')}
|
durationTitle={t('list.duration')}
|
||||||
detailButton={t('list.details')}
|
detailButton={t('list.details')}
|
||||||
/>
|
/>
|
||||||
<CustomPagination total={20} />
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,33 +1,61 @@
|
||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { useSearchParams } from 'next/navigation';
|
||||||
import { List, Tag } from 'antd';
|
import { List, Tag } from 'antd';
|
||||||
import { RightOutlined } from '@ant-design/icons';
|
import { RightOutlined } from '@ant-design/icons';
|
||||||
|
import isEqual from 'lodash/isEqual';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
import { Link } from '../../navigation';
|
import { Link } from '../../navigation';
|
||||||
import { ExpertsData } from '../../types/experts';
|
import { ExpertsData, Filter } from '../../types/experts';
|
||||||
|
import { getObjectByFilter } from '../../utils/filter';
|
||||||
|
import { getExpertsList } from '../../actions/experts';
|
||||||
|
import { CustomPagination, CustomSpin } from '../view';
|
||||||
|
|
||||||
type ExpertListProps = {
|
type ExpertListProps = {
|
||||||
data: ExpertsData;
|
data?: ExpertsData;
|
||||||
priceTitle: string;
|
priceTitle: string;
|
||||||
durationTitle: string;
|
durationTitle: string;
|
||||||
detailButton: string;
|
detailButton: string;
|
||||||
|
locale: string;
|
||||||
|
baseFilter: Filter;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const ExpertsList = ({
|
export const ExpertsList = ({
|
||||||
data,
|
data,
|
||||||
priceTitle,
|
priceTitle,
|
||||||
durationTitle,
|
durationTitle,
|
||||||
detailButton
|
detailButton,
|
||||||
|
locale,
|
||||||
|
baseFilter
|
||||||
}: ExpertListProps) => {
|
}: ExpertListProps) => {
|
||||||
|
const searchParams = useSearchParams();
|
||||||
const getTitle = (str: string, value?: any): string => (value ? str.replace('0', value) : str);
|
const getTitle = (str: string, value?: any): string => (value ? str.replace('0', value) : str);
|
||||||
|
const [experts, setExperts] = useState<ExpertsData | undefined>();
|
||||||
|
|
||||||
return (
|
useEffect(() => {
|
||||||
|
const filter = {
|
||||||
|
...baseFilter,
|
||||||
|
...getObjectByFilter(searchParams)
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!isEqual(baseFilter, filter)) {
|
||||||
|
getExpertsList(filter, locale)
|
||||||
|
.then((experts) => {
|
||||||
|
setExperts(experts);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setExperts(data);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return experts ? (
|
||||||
|
<>
|
||||||
<List
|
<List
|
||||||
itemLayout="vertical"
|
itemLayout="vertical"
|
||||||
size="large"
|
size="large"
|
||||||
className="search-result"
|
className="search-result"
|
||||||
dataSource={data}
|
dataSource={experts}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item key={item?.id} className="card-profile">
|
<List.Item key={item?.id} className="card-profile">
|
||||||
<List.Item.Meta
|
<List.Item.Meta
|
||||||
|
@ -73,5 +101,7 @@ export const ExpertsList = ({
|
||||||
</List.Item>
|
</List.Item>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
);
|
<CustomPagination total={20} />
|
||||||
|
</>
|
||||||
|
) : <CustomSpin />;
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,8 +10,8 @@ import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filt
|
||||||
import { CustomSwitch, CustomSlider } from '../view';
|
import { CustomSwitch, CustomSlider } from '../view';
|
||||||
|
|
||||||
type ExpertsFilterProps = {
|
type ExpertsFilterProps = {
|
||||||
searchData: SearchData;
|
searchData?: SearchData;
|
||||||
basePath?: string;
|
basePath: string;
|
||||||
priceTitle: string;
|
priceTitle: string;
|
||||||
durationTitle: string;
|
durationTitle: string;
|
||||||
buttonApply: string;
|
buttonApply: string;
|
||||||
|
@ -19,7 +19,7 @@ type ExpertsFilterProps = {
|
||||||
|
|
||||||
export const ExpertsFilter = ({
|
export const ExpertsFilter = ({
|
||||||
searchData,
|
searchData,
|
||||||
basePath = '/',
|
basePath,
|
||||||
priceTitle,
|
priceTitle,
|
||||||
durationTitle,
|
durationTitle,
|
||||||
buttonApply
|
buttonApply
|
||||||
|
@ -83,7 +83,7 @@ export const ExpertsFilter = ({
|
||||||
|
|
||||||
const goToFilterPage = useCallback(() => {
|
const goToFilterPage = useCallback(() => {
|
||||||
router.push({
|
router.push({
|
||||||
pathname: basePath,
|
pathname: basePath as any,
|
||||||
query: {
|
query: {
|
||||||
...filter,
|
...filter,
|
||||||
...getObjectByAdditionalFilter(searchParams)
|
...getObjectByAdditionalFilter(searchParams)
|
||||||
|
@ -116,7 +116,7 @@ export const ExpertsFilter = ({
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="b-filter">
|
<div className="b-filter">
|
||||||
{searchData.themesGroups?.length && searchData.themesGroups.map(({ id, name, tags }) => (
|
{searchData?.themesGroups?.length && searchData.themesGroups.map(({ id, name, tags }) => (
|
||||||
<div key={id}>
|
<div key={id}>
|
||||||
<h3 className="title-h3">{name}</h3>
|
<h3 className="title-h3">{name}</h3>
|
||||||
{getList(tags)}
|
{getList(tags)}
|
||||||
|
@ -127,9 +127,9 @@ export const ExpertsFilter = ({
|
||||||
<CustomSlider
|
<CustomSlider
|
||||||
range
|
range
|
||||||
step={1}
|
step={1}
|
||||||
defaultValue={[filter?.priceFrom || searchData.sessionCostMin, filter?.priceTo || searchData.sessionCostMax]}
|
defaultValue={[filter?.priceFrom || searchData?.sessionCostMin || 0, filter?.priceTo || searchData?.sessionCostMax || 0]}
|
||||||
min={searchData.sessionCostMin}
|
min={searchData?.sessionCostMin || 0}
|
||||||
max={searchData.sessionCostMax}
|
max={searchData?.sessionCostMax || 0}
|
||||||
onChange={onChangePrice}
|
onChange={onChangePrice}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -138,9 +138,9 @@ export const ExpertsFilter = ({
|
||||||
<CustomSlider
|
<CustomSlider
|
||||||
range
|
range
|
||||||
step={1}
|
step={1}
|
||||||
defaultValue={[filter?.durationFrom || searchData.sessionDurationMin, filter?.durationTo || searchData.sessionDurationMax]}
|
defaultValue={[filter?.durationFrom || searchData?.sessionDurationMin || 0, filter?.durationTo || searchData?.sessionDurationMax || 0]}
|
||||||
min={searchData.sessionDurationMin}
|
min={searchData?.sessionDurationMin || 0}
|
||||||
max={searchData.sessionDurationMax}
|
max={searchData?.sessionDurationMax || 0}
|
||||||
onChange={onChangeDuration}
|
onChange={onChangeDuration}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -3,12 +3,13 @@
|
||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
import Image from 'next/image';
|
import Image from 'next/image';
|
||||||
|
import Link from 'next/link';
|
||||||
import {Button, Modal as AntdModal, Form, notification} from 'antd';
|
import {Button, Modal as AntdModal, Form, notification} 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 } from '../view';
|
import { CustomInput, CustomInputPassword } from '../view';
|
||||||
import { getAuth } from '../../actions/auth';
|
import { getAuth } from '../../actions/auth';
|
||||||
import {setAuthToken} from "../../utils/storage/auth";
|
import { setAuthToken } from '../../utils/storage/auth';
|
||||||
|
|
||||||
type AuthModalProps = {
|
type AuthModalProps = {
|
||||||
open: boolean;
|
open: boolean;
|
||||||
|
@ -74,7 +75,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
mode,
|
mode,
|
||||||
updateMode
|
updateMode
|
||||||
}) => {
|
}) => {
|
||||||
const [form] = Form.useForm<{ login: string, password: string, name: string, surname: string, phone: string }>();
|
const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>();
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||||
const paths = usePathname().split('/');
|
const paths = usePathname().split('/');
|
||||||
|
|
||||||
|
@ -134,10 +135,9 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
||||||
<CustomInput
|
<CustomInputPassword
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -176,25 +176,6 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
{mode === 'register' && (
|
{mode === 'register' && (
|
||||||
<>
|
<>
|
||||||
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
|
<Form form={form} autoComplete="off" style={{ display: 'flex', gap: 16, flexDirection: 'column' }}>
|
||||||
<Form.Item name="name" noStyle>
|
|
||||||
<CustomInput
|
|
||||||
size="small"
|
|
||||||
placeholder="Name"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="surname" noStyle>
|
|
||||||
<CustomInput
|
|
||||||
size="small"
|
|
||||||
placeholder="Surname"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="phone" noStyle>
|
|
||||||
<CustomInput
|
|
||||||
size="small"
|
|
||||||
placeholder="Phone"
|
|
||||||
type="phone"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="login" rules={[{ required: true }]} noStyle>
|
<Form.Item name="login" rules={[{ required: true }]} noStyle>
|
||||||
<CustomInput
|
<CustomInput
|
||||||
size="small"
|
size="small"
|
||||||
|
@ -202,10 +183,15 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
||||||
<CustomInput
|
<CustomInputPassword
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item name="confirmPassword" rules={[{ required: true }]} noStyle>
|
||||||
|
<CustomInputPassword
|
||||||
|
size="small"
|
||||||
|
placeholder="Confirm password"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -214,22 +200,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
>
|
>
|
||||||
Register
|
Register
|
||||||
</FilledButton>
|
</FilledButton>
|
||||||
<span>or</span>
|
<OutlinedButton onClick={() => updateMode('enter')}>Enter</OutlinedButton>
|
||||||
<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 === 'reset' && (
|
{mode === 'reset' && (
|
||||||
|
@ -242,10 +213,9 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
<Form.Item name="password" rules={[{ required: true }]} noStyle>
|
||||||
<CustomInput
|
<CustomInputPassword
|
||||||
size="small"
|
size="small"
|
||||||
placeholder="Password"
|
placeholder="Password"
|
||||||
type="password"
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</Form>
|
</Form>
|
||||||
|
@ -277,7 +247,7 @@ export const AuthModal: FC<AuthModalProps> = ({
|
||||||
)}
|
)}
|
||||||
<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
|
||||||
User Agreement, Privacy Policy
|
User Agreement, <Link href={'/docs/BBUDDY_privacy_policy_fin.docx' as any}>Privacy Policy</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
|
|
|
@ -2,10 +2,11 @@
|
||||||
|
|
||||||
import React, { FC, useState, useEffect } from 'react';
|
import React, { FC, useState, useEffect } from 'react';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
|
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||||
import { styled } from 'styled-components';
|
import { styled } from 'styled-components';
|
||||||
import { AuthModal } from '../../Modals/AuthModal';
|
import { AuthModal } from '../../Modals/AuthModal';
|
||||||
import {checkAuthToken} from "../../../utils/storage/auth";
|
import { checkAuthToken } from '../../../utils/storage/auth';
|
||||||
import {Link} from "../../../navigation";
|
import { Link } from '../../../navigation';
|
||||||
|
|
||||||
type HeaderAuthLinksProps = {
|
type HeaderAuthLinksProps = {
|
||||||
enterTitle: string;
|
enterTitle: string;
|
||||||
|
@ -32,6 +33,8 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
|
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
|
||||||
const [mode, setMode] = useState<'enter' | 'register' | 'reset' | 'finish'>('enter');
|
const [mode, setMode] = useState<'enter' | 'register' | 'reset' | 'finish'>('enter');
|
||||||
|
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||||
|
const pathname = selectedLayoutSegment || '';
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isOpenModal) {
|
if (!isOpenModal) {
|
||||||
|
@ -47,7 +50,7 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
|
||||||
return checkAuthToken()
|
return checkAuthToken()
|
||||||
? (
|
? (
|
||||||
<li>
|
<li>
|
||||||
<Link href={'/sessions' as any}>{accountTitle}</Link>
|
<Link href={'/account' as any} className={pathname === 'account' ? 'active' : ''}>{accountTitle}</Link>
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
: (
|
: (
|
||||||
|
|
|
@ -1,20 +1,38 @@
|
||||||
import React, { ReactNode } from 'react';
|
'use client';
|
||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||||
|
import { Link } from '../../../navigation';
|
||||||
import { HeaderAuthLinks } from './HeaderAuthLinks';
|
import { HeaderAuthLinks } from './HeaderAuthLinks';
|
||||||
|
|
||||||
type HeaderMenuProps = {
|
type HeaderMenuProps = {
|
||||||
children: ReactNode;
|
|
||||||
enterTitle: string;
|
enterTitle: string;
|
||||||
registerTitle: string;
|
registerTitle: string;
|
||||||
accountTitle: string;
|
accountTitle: string;
|
||||||
|
linkConfig: { path: string, title: string }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeaderMenu = ({ children, enterTitle, registerTitle, accountTitle }: HeaderMenuProps) => (
|
export const HeaderMenu = ({
|
||||||
|
enterTitle,
|
||||||
|
registerTitle,
|
||||||
|
accountTitle,
|
||||||
|
linkConfig
|
||||||
|
}: HeaderMenuProps) => {
|
||||||
|
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||||
|
const pathname = selectedLayoutSegment || '';
|
||||||
|
|
||||||
|
return (
|
||||||
<div className="b-header__nav">
|
<div className="b-header__nav">
|
||||||
<nav>
|
<nav>
|
||||||
<ul className="b-header__nav__list ">
|
<ul className="b-header__nav__list ">
|
||||||
{children}
|
{linkConfig.map(({ path, title }) => (
|
||||||
|
<li key={path}>
|
||||||
|
<Link href={`/${path}` as any} className={pathname === path ? 'active' : ''}>{title}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
<HeaderAuthLinks enterTitle={enterTitle} registerTitle={registerTitle} accountTitle={accountTitle} />
|
<HeaderAuthLinks enterTitle={enterTitle} registerTitle={registerTitle} accountTitle={accountTitle} />
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -1,17 +1,26 @@
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
import React, { FC, ReactNode, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
|
import { useSelectedLayoutSegment } from 'next/navigation';
|
||||||
import { HeaderAuthLinks } from './HeaderAuthLinks';
|
import { HeaderAuthLinks } from './HeaderAuthLinks';
|
||||||
|
import { Link } from '../../../navigation';
|
||||||
|
|
||||||
type HeaderMenuMobileProps = {
|
type HeaderMenuMobileProps = {
|
||||||
children: ReactNode;
|
linkConfig: { path: string, title: string }[];
|
||||||
enterTitle: string;
|
enterTitle: string;
|
||||||
registerTitle: string;
|
registerTitle: string;
|
||||||
accountTitle: string;
|
accountTitle: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const HeaderMobileMenu: FC<HeaderMenuMobileProps> = ({ children, enterTitle, registerTitle, accountTitle }) => {
|
export const HeaderMobileMenu: FC<HeaderMenuMobileProps> = ({
|
||||||
|
linkConfig,
|
||||||
|
enterTitle,
|
||||||
|
registerTitle,
|
||||||
|
accountTitle
|
||||||
|
}) => {
|
||||||
const [showMobileMenu, setShowMobileMenu] = useState<boolean>(false);
|
const [showMobileMenu, setShowMobileMenu] = useState<boolean>(false);
|
||||||
|
const selectedLayoutSegment = useSelectedLayoutSegment();
|
||||||
|
const pathname = selectedLayoutSegment || '';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -40,7 +49,11 @@ export const HeaderMobileMenu: FC<HeaderMenuMobileProps> = ({ children, enterTit
|
||||||
</div>
|
</div>
|
||||||
<div className="menu-mobile__body">
|
<div className="menu-mobile__body">
|
||||||
<ul className="menu-mobile__list">
|
<ul className="menu-mobile__list">
|
||||||
{children}
|
{linkConfig.map(({ path, title }) => (
|
||||||
|
<li key={path}>
|
||||||
|
<Link href={`/${path}` as any} className={pathname === path ? 'active' : ''}>{title}</Link>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -13,11 +13,10 @@ type HeaderProps = {
|
||||||
export const Header: FC<HeaderProps> = ({ locale }) => {
|
export const Header: FC<HeaderProps> = ({ locale }) => {
|
||||||
const t = useTranslations('Header');
|
const t = useTranslations('Header');
|
||||||
|
|
||||||
const routes = HEAD_ROUTES.map((item) => (
|
const routes: { path: string, title: string }[] = HEAD_ROUTES.map((item) => ({
|
||||||
<li key={item}>
|
path: item,
|
||||||
<Link href={`/${item}` as any}>{t(`menu.${item}`)}</Link>
|
title: t(`menu.${item}`)
|
||||||
</li>
|
}));
|
||||||
));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -30,15 +29,21 @@ export const Header: FC<HeaderProps> = ({ locale }) => {
|
||||||
alt=""
|
alt=""
|
||||||
/>
|
/>
|
||||||
</Link>
|
</Link>
|
||||||
<HeaderMenu enterTitle={t('enter')} registerTitle={t('registration')} accountTitle={t('account')}>
|
<HeaderMenu
|
||||||
{routes}
|
enterTitle={t('enter')}
|
||||||
</HeaderMenu>
|
registerTitle={t('registration')}
|
||||||
|
accountTitle={t('account')}
|
||||||
|
linkConfig={routes}
|
||||||
|
/>
|
||||||
<LanguageSwitcher locale={locale} />
|
<LanguageSwitcher locale={locale} />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
<HeaderMobileMenu enterTitle={t('enter')} registerTitle={t('registration')} accountTitle={t('account')}>
|
<HeaderMobileMenu
|
||||||
{routes}
|
enterTitle={t('enter')}
|
||||||
</HeaderMobileMenu>
|
registerTitle={t('registration')}
|
||||||
|
accountTitle={t('account')}
|
||||||
|
linkConfig={routes}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,45 @@
|
||||||
|
import React from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Input as AntdInput } from 'antd';
|
||||||
|
import { EyeOutlined, EyeInvisibleOutlined } from '@ant-design/icons';
|
||||||
|
|
||||||
|
const Input = styled(AntdInput.Password)`
|
||||||
|
padding: 15px 16px !important;
|
||||||
|
background: #F8F8F7 !important;
|
||||||
|
border: 1px solid #F8F8F7 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
color: #000 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
input {
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus, &:hover, &.ant-input-affix-wrapper-focused {
|
||||||
|
border-color: #66A5AD !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
input::placeholder {
|
||||||
|
color: #000 !important;
|
||||||
|
opacity: .4 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.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}} />
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
|
@ -0,0 +1,106 @@
|
||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Select as AntdSelect, Tag } from 'antd';
|
||||||
|
import type { SelectProps } from 'antd';
|
||||||
|
|
||||||
|
type TagRender = SelectProps['tagRender'];
|
||||||
|
|
||||||
|
const Select = styled(AntdSelect)`
|
||||||
|
width: 100% !important;
|
||||||
|
height: 54px !important;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
background-color: #F8F8F7 !important;
|
||||||
|
border-color: #F8F8F7 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 22px 16px 8px !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
.ant-select-selection-item {
|
||||||
|
font-size: 15px !important;
|
||||||
|
font-weight: 400 !important;
|
||||||
|
line-height: 24px !important;
|
||||||
|
color: #313131 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-selection-overflow-item {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-arrow {
|
||||||
|
color: #2c7873 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-select-focused, &:hover {
|
||||||
|
.ant-select-selector {
|
||||||
|
border-color: #2c7873 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectWrap = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectLabel = styled.div`
|
||||||
|
font-size: 15px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #000;
|
||||||
|
opacity: .3;
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 15px;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all .1s ease;
|
||||||
|
|
||||||
|
.b-multiselect__active & {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 14px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const tagRender: TagRender = (props) => {
|
||||||
|
const { label } = props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tag className="skills__list__item">
|
||||||
|
{label}
|
||||||
|
</Tag>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CustomMultiSelect = (props: any) => {
|
||||||
|
const { label, value, ...other } = props;
|
||||||
|
const [isActiveLabel, setIsActiveLabel] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (label) {
|
||||||
|
setIsActiveLabel(!!value?.length);
|
||||||
|
} else {
|
||||||
|
setIsActiveLabel(false);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectWrap className={isActiveLabel ? 'b-multiselect__active' : ''}>
|
||||||
|
<SelectLabel>{label}</SelectLabel>
|
||||||
|
<Select
|
||||||
|
mode="multiple"
|
||||||
|
value={value}
|
||||||
|
showSearch={false}
|
||||||
|
maxTagCount="responsive"
|
||||||
|
tagRender={tagRender}
|
||||||
|
onFocus={!isActiveLabel ? () => setIsActiveLabel(true) : undefined}
|
||||||
|
onBlur={() => setIsActiveLabel(!!value?.length)}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
</SelectWrap>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,85 @@
|
||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import styled from 'styled-components';
|
||||||
|
import { Select as AntdSelect} from 'antd';
|
||||||
|
|
||||||
|
const Select = styled(AntdSelect)`
|
||||||
|
width: 100% !important;
|
||||||
|
height: 54px !important;
|
||||||
|
|
||||||
|
.ant-select-selector {
|
||||||
|
background-color: #F8F8F7 !important;
|
||||||
|
border-color: #F8F8F7 !important;
|
||||||
|
border-radius: 8px !important;
|
||||||
|
padding: 22px 16px 8px !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
|
||||||
|
.ant-select-selection-item {
|
||||||
|
font-size: 15px !important;
|
||||||
|
font-weight: 400 !important;
|
||||||
|
line-height: 24px !important;
|
||||||
|
color: #313131 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.ant-select-arrow {
|
||||||
|
color: #2c7873 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.ant-select-focused, &:hover {
|
||||||
|
.ant-select-selector {
|
||||||
|
border-color: #2c7873 !important;
|
||||||
|
box-shadow: none !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectWrap = styled.div`
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectLabel = styled.div`
|
||||||
|
font-size: 15px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
color: #000;
|
||||||
|
opacity: .3;
|
||||||
|
position: absolute;
|
||||||
|
left: 16px;
|
||||||
|
top: 15px;
|
||||||
|
z-index: 1;
|
||||||
|
transition: all .1s ease;
|
||||||
|
|
||||||
|
.b-select__active & {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 300;
|
||||||
|
line-height: 14px;
|
||||||
|
top: 8px;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CustomSelect = (props: any) => {
|
||||||
|
const { label, value, ...other } = props;
|
||||||
|
const [isActiveLabel, setIsActiveLabel] = useState<boolean>(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (label) {
|
||||||
|
setIsActiveLabel(!!value);
|
||||||
|
} else {
|
||||||
|
setIsActiveLabel(false);
|
||||||
|
}
|
||||||
|
}, [value]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SelectWrap className={isActiveLabel ? 'b-select__active' : ''}>
|
||||||
|
<SelectLabel>{label}</SelectLabel>
|
||||||
|
<Select
|
||||||
|
value={value}
|
||||||
|
onFocus={!isActiveLabel ? () => setIsActiveLabel(true) : undefined}
|
||||||
|
onBlur={() => setIsActiveLabel(!!value)}
|
||||||
|
{...other}
|
||||||
|
/>
|
||||||
|
</SelectWrap>
|
||||||
|
);
|
||||||
|
};
|
|
@ -0,0 +1,12 @@
|
||||||
|
import React from 'react';
|
||||||
|
import { Spin as AntdSpin } from 'antd';
|
||||||
|
import { styled } from 'styled-components';
|
||||||
|
|
||||||
|
const Spin = styled(AntdSpin)`
|
||||||
|
width: 100%;
|
||||||
|
margin: 24px 0;
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const CustomSpin = (props: any) => (
|
||||||
|
<Spin {...props} />
|
||||||
|
);
|
|
@ -5,3 +5,7 @@ export * from './CustomSlider';
|
||||||
export * from './CustomPagination';
|
export * from './CustomPagination';
|
||||||
export * from './CustomRate';
|
export * from './CustomRate';
|
||||||
export * from './CustomInput';
|
export * from './CustomInput';
|
||||||
|
export * from './CustomInputPassword';
|
||||||
|
export * from './CustomSelect';
|
||||||
|
export * from './CustomMultiSelect';
|
||||||
|
export * from './CustomSpin';
|
||||||
|
|
|
@ -9,5 +9,4 @@ export default createMiddleware({
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
matcher: ['/', '/(en|ru|de|it|es|fr)/:path*']
|
matcher: ['/', '/(en|ru|de|it|es|fr)/:path*']
|
||||||
// matcher: ['/', '/(en|ru|de|it|es|fr).html']
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -15,6 +15,10 @@ body{
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #66A5AD !important;
|
||||||
|
}
|
||||||
|
|
||||||
.b-wrapper {
|
.b-wrapper {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -426,21 +430,21 @@ body{
|
||||||
|
|
||||||
.btn-apply {
|
.btn-apply {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
outline: none;
|
outline: none !important;
|
||||||
border: none;
|
border: none !important;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: 8px;
|
border-radius: 8px !important;
|
||||||
background: #FFBD00;
|
background: #FFBD00 !important;
|
||||||
box-shadow: 0px 2px 4px 0px rgba(252, 214, 70, 0.16);
|
box-shadow: 0px 2px 4px 0px rgba(252, 214, 70, 0.16) !important;
|
||||||
display: flex;
|
display: flex;
|
||||||
gap: 10px;
|
gap: 10px;
|
||||||
height: 54px;
|
height: 54px !important;
|
||||||
padding: 8px 31px;
|
padding: 8px 31px;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
color: #003B46;
|
color: #003B46 !important;
|
||||||
@include rem(15);
|
@include rem(15);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|
|
@ -70,7 +70,7 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: #003B46;
|
color: #003B46 !important;
|
||||||
@include rem(16);
|
@include rem(16);
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
|
|
|
@ -66,6 +66,10 @@
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
color: #003B46;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
|
|
|
@ -199,7 +199,7 @@
|
||||||
@include rem(14);
|
@include rem(14);
|
||||||
border-radius: 24px;
|
border-radius: 24px;
|
||||||
gap: 6px;
|
gap: 6px;
|
||||||
color: $white;
|
color: $white !important;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
line-height: 133.333%;
|
line-height: 133.333%;
|
||||||
|
|
|
@ -62,7 +62,7 @@ export const getObjectByFilter = (searchParams?: any): Filter | undefined => {
|
||||||
let tags = searchParams?.getAll('themesTagIds');
|
let tags = searchParams?.getAll('themesTagIds');
|
||||||
|
|
||||||
if (tags && tags.length > 0) {
|
if (tags && tags.length > 0) {
|
||||||
filter.themesTagIds = tags.map((id) => Number(id));
|
filter.themesTagIds = tags.map((id: string) => Number(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (searchParams?.has('priceFrom')) {
|
if (searchParams?.has('priceFrom')) {
|
||||||
|
|
|
@ -7,7 +7,7 @@ export function checkAuthToken() {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getAuthToken() {
|
export function getAuthToken() {
|
||||||
return 'tr';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// export function getAuthToken() {
|
// export function getAuthToken() {
|
||||||
|
@ -18,6 +18,6 @@ export function getAuthToken() {
|
||||||
// localStorage.removeItem(AUTH_TOKEN_KEY);
|
// localStorage.removeItem(AUTH_TOKEN_KEY);
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// export function setAuthToken(token: string) {
|
export function setAuthToken(token: string) {
|
||||||
// localStorage.setItem(AUTH_TOKEN_KEY, token);
|
// localStorage.setItem(AUTH_TOKEN_KEY, token);
|
||||||
// }
|
}
|
||||||
|
|
Loading…
Reference in New Issue