feat: add app localization

This commit is contained in:
SD 2024-04-04 02:18:18 +04:00
parent 2b814a09f2
commit b74c86789f
71 changed files with 1026 additions and 763 deletions

View File

@ -1,14 +1,4 @@
{
"Header": {
"registration": "Registration",
"enter": "Enter",
"account": "My Account",
"menu": {
"bb-client": "Start grow with BB",
"bb-expert": "Become BB Expert",
"blog": "Blog&News"
}
},
"Main": {
"title": "Bbuddy - Main",
"description": "Bbuddy desc",
@ -29,15 +19,6 @@
"header-desc": "B Buddy is an application for active people interested in growth on all fronts — personal, professional and business. Right now they are looking for you."
},
"Account": {
"menu": {
"sessions": "Upcoming Sessions",
"notifications": "Notification",
"support": "Help & Support",
"information": "Legal Information",
"settings": "Profile Settings",
"messages": "Messages",
"work-with-us": "Work With Us"
},
"WorkWithUs": {
"title": "Become a BBuddy Expert",
"insert-info": "Insert your personal information to start your journey as a BBuddy Expert",

47
package-lock.json generated
View File

@ -10,6 +10,7 @@
"dependencies": {
"@ant-design/cssinjs": "^1.18.1",
"@ant-design/icons": "^5.2.6",
"@ant-design/nextjs-registry": "^1.0.0",
"antd": "^5.12.1",
"antd-img-crop": "^4.21.0",
"axios": "^1.6.5",
@ -56,15 +57,15 @@
}
},
"node_modules/@ant-design/cssinjs": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.1.tgz",
"integrity": "sha512-1JURAPrsjK1GwpqByTq3bJ7nF7lbMKDZpehqeR2n8/IR5O58/W1U4VcOeaw5ZyTHri3tEMcom7dyP2tvxpW54g==",
"version": "1.18.5",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.5.tgz",
"integrity": "sha512-Ub4n3d+MAX/qtE5S9PM8iOn5ocU7GUAIC4Adc2X8UCMXnsRRfpJBHsBdtQ1qoAuaQ7lU2M1BTCuJ+fkv4fOWiw==",
"dependencies": {
"@babel/runtime": "^7.11.1",
"@emotion/hash": "^0.8.0",
"@emotion/unitless": "^0.7.5",
"classnames": "^2.3.1",
"csstype": "3.1.2",
"csstype": "^3.1.3",
"rc-util": "^5.35.0",
"stylis": "^4.0.13"
},
@ -97,6 +98,18 @@
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.3.1.tgz",
"integrity": "sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g=="
},
"node_modules/@ant-design/nextjs-registry": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/nextjs-registry/-/nextjs-registry-1.0.0.tgz",
"integrity": "sha512-kU1K1UOhwrF6DPv73MhuL5a6U4e6/TiFapeLUt/c/kch9h5qFwEaJPb4RSJKNw0PRBfqCAPS011wVm4wYcrqbQ==",
"peerDependencies": {
"@ant-design/cssinjs": "^1.18.2",
"antd": "^5.0.0",
"next": "^14.0.0",
"react": ">=16.0.0",
"react-dom": ">=16.0.0"
}
},
"node_modules/@ant-design/react-slick": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz",
@ -1524,9 +1537,9 @@
}
},
"node_modules/csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@ -5355,15 +5368,15 @@
}
},
"@ant-design/cssinjs": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.1.tgz",
"integrity": "sha512-1JURAPrsjK1GwpqByTq3bJ7nF7lbMKDZpehqeR2n8/IR5O58/W1U4VcOeaw5ZyTHri3tEMcom7dyP2tvxpW54g==",
"version": "1.18.5",
"resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.18.5.tgz",
"integrity": "sha512-Ub4n3d+MAX/qtE5S9PM8iOn5ocU7GUAIC4Adc2X8UCMXnsRRfpJBHsBdtQ1qoAuaQ7lU2M1BTCuJ+fkv4fOWiw==",
"requires": {
"@babel/runtime": "^7.11.1",
"@emotion/hash": "^0.8.0",
"@emotion/unitless": "^0.7.5",
"classnames": "^2.3.1",
"csstype": "3.1.2",
"csstype": "^3.1.3",
"rc-util": "^5.35.0",
"stylis": "^4.0.13"
}
@ -5385,6 +5398,12 @@
"resolved": "https://registry.npmjs.org/@ant-design/icons-svg/-/icons-svg-4.3.1.tgz",
"integrity": "sha512-4QBZg8ccyC6LPIRii7A0bZUk3+lEDCLnhB+FVsflGdcWPPmV+j3fire4AwwoqHV/BibgvBmR9ZIo4s867smv+g=="
},
"@ant-design/nextjs-registry": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ant-design/nextjs-registry/-/nextjs-registry-1.0.0.tgz",
"integrity": "sha512-kU1K1UOhwrF6DPv73MhuL5a6U4e6/TiFapeLUt/c/kch9h5qFwEaJPb4RSJKNw0PRBfqCAPS011wVm4wYcrqbQ==",
"requires": {}
},
"@ant-design/react-slick": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@ant-design/react-slick/-/react-slick-1.0.2.tgz",
@ -6413,9 +6432,9 @@
}
},
"csstype": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
},
"damerau-levenshtein": {
"version": "1.0.8",

View File

@ -11,6 +11,7 @@
"dependencies": {
"@ant-design/cssinjs": "^1.18.1",
"@ant-design/icons": "^5.2.6",
"@ant-design/nextjs-registry": "^1.0.0",
"antd": "^5.12.1",
"antd-img-crop": "^4.21.0",
"axios": "^1.6.5",

View File

@ -9,13 +9,13 @@ export default function Directions() {
<div className="main-popular__coll">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular1.png" alt=""/>
<img src="/images/popular1.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Work Life Balance</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>
@ -23,13 +23,13 @@ export default function Directions() {
<div className="main-popular__coll">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular2.png" alt=""/>
<img src="/images/popular2.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Strategic Session</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>
@ -37,13 +37,13 @@ export default function Directions() {
<div className="main-popular__coll d-none d-md-block">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular3.png" alt=""/>
<img src="/images/popular3.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Personal Growth</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>
@ -51,13 +51,13 @@ export default function Directions() {
<div className="main-popular__coll d-none d-lg-block">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular4.png" alt=""/>
<img src="/images/popular4.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Career Planning</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>
@ -65,13 +65,13 @@ export default function Directions() {
<div className="main-popular__coll d-none d-lg-block">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular5.png" alt=""/>
<img src="/images/popular5.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Executive Coaching</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>
@ -79,13 +79,13 @@ export default function Directions() {
<div className="main-popular__coll d-none d-xl-block">
<div className="b-popular">
<div className="b-popular__image">
<img className="" src="/images/popular6.png" alt=""/>
<img src="/images/popular6.png" alt=""/>
</div>
<div className="b-popular__inner">
<div className="b-popular__title">Career Development</div>
<div className="b-popular__wrap-link">
<a className="b-popular__link" href="">23 experts</a> |
<a className="b-popular__link" href="">245 offers</a>
<a className="b-popular__link" href="#">23 experts</a> |
<a className="b-popular__link" href="#">245 offers</a>
</div>
</div>
</div>

View File

@ -1,19 +1,20 @@
import React from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Information',
description: 'Bbuddy desc information'
};
export default function Information() {
export default function Information({ params: { locale } }: { params: { locale: string } }) {
const t = useTranslations('Account.LegalInformation');
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.information', locale)}</li>
</ol>
<div className="base-text">
Welcome to the B BUDDY LTDs privacy policy. <br />

View File

@ -29,7 +29,7 @@ export default function AccountInnerLayout({ children, params: { locale } }: Acc
<div className="b-inner">
<div className="row">
<div className="col-xl-3 col-lg-4 d-none d-lg-block">
<AccountMenu menu={getMenuConfig()} />
<AccountMenu menu={getMenuConfig()} locale={locale} />
</div>
<div className="col-xl-9 col-lg-8 ">
<div className="page-account__inner">

View File

@ -1,6 +1,7 @@
import React from 'react';
import { useTranslations } from 'next-intl';
import { Link } from '../../../../../../navigation';
import { i18nText } from '../../../../../../i18nKeys';
export function generateStaticParams({
params: { locale },
@ -15,7 +16,7 @@ export function generateStaticParams({
return result;
}
export default function Message({ params }: { params: { textId: string } }) {
export default function Message({ params }: { params: { locale: string, textId: string } }) {
const t = useTranslations('Account.Messages');
return (
@ -23,7 +24,7 @@ export default function Message({ params }: { params: { textId: string } }) {
<ol className="breadcrumb">
<li className="breadcrumb-item">
<Link href={'/account/messages' as any}>
{t('title')}
{i18nText('accountMenu.messages', params.locale)}
</Link>
</li>
<li className="breadcrumb-item active" aria-current="page">{`Person ${params.textId}`}</li>

View File

@ -2,23 +2,24 @@ import React, { Suspense } from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { Link } from '../../../../../navigation';
import { CustomInput } from '../../../../../components/view';
import { CustomInput } from '../../../../../components/view/CustomInput';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Messages',
description: 'Bbuddy desc messages'
};
export default function Messages() {
export default function Messages({ params: { locale } }: { params: { locale: string } }) {
const t = useTranslations('Account.Messages');
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.messages', locale)}</li>
</ol>
<Suspense>
<CustomInput placeholder="Name" />
<CustomInput placeholder={i18nText('name', locale)} />
</Suspense>
<div className="messages-session">
<Link
@ -30,6 +31,7 @@ export default function Messages() {
<img src="/images/person.png" className="" alt="" />
</div>
<div className="card-profile__header__inner">
<div style={{ width: '100%' }}>
<div className="card-profile__header__name">
David
<span className="count">14</span>
@ -42,6 +44,7 @@ export default function Messages() {
</div>
</div>
</div>
</div>
</Link>
<Link
className="card-profile"
@ -52,6 +55,7 @@ export default function Messages() {
<img src="/images/person.png" className="" alt="" />
</div>
<div className="card-profile__header__inner">
<div style={{ width: '100%' }}>
<div className="card-profile__header__name">David</div>
<div className="card-profile__header__title">
Lorem ipsum dolor sit at, consecte...
@ -61,6 +65,7 @@ export default function Messages() {
</div>
</div>
</div>
</div>
</Link>
<Link
className="card-profile"
@ -71,6 +76,7 @@ export default function Messages() {
<img src="/images/person.png" className="" alt="" />
</div>
<div className="card-profile__header__inner">
<div style={{ width: '100%' }}>
<div className="card-profile__header__name">David</div>
<div className="card-profile__header__title">
Lorem ipsum dolor sit at, consecte...
@ -80,6 +86,7 @@ export default function Messages() {
</div>
</div>
</div>
</div>
</Link>
</div>
</>

View File

@ -1,19 +1,26 @@
import React from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import dayjs from 'dayjs';
import 'dayjs/locale/ru';
import 'dayjs/locale/en';
import 'dayjs/locale/de';
import 'dayjs/locale/it';
import 'dayjs/locale/fr';
import 'dayjs/locale/es';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Notifications',
description: 'Bbuddy desc notifications'
};
export default function Notifications() {
const t = useTranslations('Account.Notifications');
export default function Notifications({ params: { locale } }: { params: { locale: string } }) {
const date = dayjs('2022-05-22').locale(locale);
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.notifications', locale)}</li>
</ol>
<div className="list-notifications ">
<div className="b-notifications primary">
@ -21,9 +28,9 @@ export default function Notifications() {
<div className="b-notifications__text">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc auctor leo eu justo molestie
</div>
<div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__date">{date.format('D MMMM YYYY')}</div>
<div className="b-notifications__inner">
<a href="#" className="b-notifications__delete">{t('delete')}</a>
<a href="#" className="b-notifications__delete">{i18nText('delete', locale)}</a>
</div>
</div>
<div className="b-notifications primary">
@ -31,9 +38,9 @@ export default function Notifications() {
<div className="b-notifications__text">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc auctor leo eu justo molestie
</div>
<div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__date">{date.format('D MMMM YYYY')}</div>
<div className="b-notifications__inner">
<a href="#" className="b-notifications__delete">{t('delete')}</a>
<a href="#" className="b-notifications__delete">{i18nText('delete', locale)}</a>
</div>
</div>
<div className="b-notifications danger">
@ -41,9 +48,9 @@ export default function Notifications() {
<div className="b-notifications__text">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc auctor leo eu justo molestie
</div>
<div className="b-notifications__date">25 may 2022</div>
<div className="b-notifications__date">{date.format('D MMMM YYYY')}</div>
<div className="b-notifications__inner">
<a href="#" className="b-notifications__delete">{t('delete')}</a>
<a href="#" className="b-notifications__delete">{i18nText('delete', locale)}</a>
</div>
</div>
</div>

View File

@ -15,13 +15,6 @@ export default function Sessions({ params: { locale } }: { params: { locale: str
<Suspense fallback={<p>Loading...</p>}>
<SessionsTabs
locale={locale}
intlConfig={{
upcoming: t('upcoming-sessions'),
requested: t('sessions-requested'),
recent: t('recent-sessions'),
selectTopicLabel: t('topic'),
dateLabel: t('day-start')
}}
/>
</Suspense>
);

View File

@ -1,31 +1,29 @@
import React from 'react';
import { useTranslations } from 'next-intl';
import { Link } from '../../../../../../navigation';
import { i18nText } from '../../../../../../i18nKeys/';
export default function ChangePassword({ params }: { params: { userId: string } }) {
const t = useTranslations('Account.Settings');
export default function ChangePassword({ params: { locale } }: { params: { locale: string } }) {
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item">
<Link href={`/${params.userId}/settings` as any}>
{t('title')}
<Link href={'/account/settings' as any}>
{i18nText('accountMenu.settings', locale)}
</Link>
</li>
<li className="breadcrumb-item active" aria-current="page">{t('change-password')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('changePass', locale)}</li>
</ol>
<form className="form-settings" action="">
<div className="form-field password-hide">
<input type="text" placeholder={t('old-password')} className="base-input" id="" value="" />
<input type="text" placeholder={i18nText('oldPass', locale)} className="base-input" id="" value="" />
</div>
<div className="form-field password-hide">
<input type="text" placeholder={t('new-password')} className="base-input" id="" value="" />
<input type="text" placeholder={i18nText('newPass', locale)} className="base-input" id="" value="" />
</div>
<div className="form-field password-show">
<input className="base-input" type="text" placeholder={t('confirm-password')} id="" value="" />
<input className="base-input" type="text" placeholder={i18nText('confirmPass', locale)} id="" value="" />
</div>
<button className="btn-apply">{t('save')}</button>
<button className="btn-apply">{i18nText('save', locale)}</button>
</form>
</>
);

View File

@ -2,6 +2,7 @@ import React, { Suspense } from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { ProfileSettings } from '../../../../../components/Account';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Profile Settings',
@ -14,19 +15,10 @@ export default function Settings({ params: { locale } }: { params: { locale: str
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.settings', locale)}</li>
</ol>
<Suspense>
<ProfileSettings
locale={locale}
photoDesc={t('photo-desc')}
placeholderName={t('name')}
placeholderSurname={t('surname')}
placeholderBirthday={t('birthday')}
placeholderEmail={t('email')}
changePasswordLink={t('change-password')}
saveButton={t('save')}
/>
<ProfileSettings locale={locale} />
</Suspense>
</>
);

View File

@ -1,19 +1,17 @@
import React from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Help & Support',
description: 'Bbuddy desc help & support'
};
export default function Support() {
const t = useTranslations('Account.Support');
export default function Support({ params: { locale } }: { params: { locale: string } }) {
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.support', locale)}</li>
</ol>
<div className="base-text">
some text

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link } from '../../../../../../../navigation';
import { CustomSelect } from '../../../../../../../components/view';
import { CustomSelect } from '../../../../../../../components/view/CustomSelect';
export default function AddOffer() {
return (

View File

@ -1,6 +1,6 @@
import React from 'react';
import { Link } from '../../../../../../navigation';
import { CustomSelect } from '../../../../../../components/view';
import { CustomSelect } from '../../../../../../components/view/CustomSelect';
export default function NewTopic() {
return (

View File

@ -1,27 +1,28 @@
import React from 'react';
import type { Metadata } from 'next';
import { useTranslations } from 'next-intl';
import { i18nText } from '../../../../../i18nKeys';
export const metadata: Metadata = {
title: 'Bbuddy - Account - Work with us',
description: 'Bbuddy desc work with us'
};
export default function WorkWithUs() {
export default function WorkWithUs({ params: { locale } }: { params: { locale: string } }) {
const t = useTranslations('Account.WorkWithUs');
return (
<>
<ol className="breadcrumb">
<li className="breadcrumb-item active" aria-current="page">{t('title')}</li>
<li className="breadcrumb-item active" aria-current="page">{i18nText('accountMenu.work-with-us', locale)}</li>
</ol>
<div className="b-info">
<div className="image-info">
<img className="" src="/images/info.png" alt="" />
</div>
<div className="b-info__title">{t('insert-info')}</div>
<button className="btn-apply">{t('start')}</button>
<div className="base-text">{t('base-text')}</div>
<div className="b-info__title">{i18nText('insertInfo', locale)}</div>
<button className="btn-apply">{i18nText('getStarted', locale)}</button>
<div className="base-text">{i18nText('changeUserData', locale)}</div>
</div>
</>
);

View File

@ -9,7 +9,7 @@ import {
ExpertPractice
} from '../../../../components/Experts/ExpertDetails';
import { Details } from '../../../../types/experts';
import { BackButton } from '../../../../components/view';
import { BackButton } from '../../../../components/view/BackButton';
export const metadata: Metadata = {
title: 'Bbuddy - Experts item',

View File

@ -3,10 +3,9 @@ import { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { ConfigProvider } from 'antd';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import theme from '../../constants/theme';
import { ALLOWED_LOCALES } from '../../constants/locale';
import StyledComponentsRegistry from '../../lib/AntdRegistry';
import StyledRegistry from '../../lib/StyleRegistry';
import { Header, Footer } from '../../components/Page';
type LayoutProps = {
@ -28,18 +27,16 @@ export default function LocaleLayout({ children, params: { locale } }: LayoutPro
unstable_setRequestLocale(locale);
return (
<StyledRegistry>
<StyledComponentsRegistry>
<AntdRegistry>
<ConfigProvider theme={theme}>
<div className="b-wrapper">
<div className="b-content">
<Header locale={locale} />
{children}
</div>
<Footer />
<Footer locale={locale} />
</div>
</ConfigProvider>
</StyledComponentsRegistry>
</StyledRegistry>
</AntdRegistry>
);
};

View File

@ -3,9 +3,9 @@
import React from 'react';
import Link from 'next/link';
import { Button, ConfigProvider } from 'antd';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import { comfortaa, inter } from './fonts';
import StyledRegistry from '../lib/StyleRegistry';
import StyledComponentsRegistry from '../lib/AntdRegistry';
import theme from '../constants/theme';
export default function GlobalError({
@ -18,8 +18,8 @@ export default function GlobalError({
return (
<html className={`${comfortaa.variable} ${inter.variable}`}>
<body>
<AntdRegistry>
<StyledRegistry>
<StyledComponentsRegistry>
<ConfigProvider theme={theme}>
<div className="b-wrapper">
<div className="b-content">
@ -57,8 +57,8 @@ export default function GlobalError({
</footer>
</div>
</ConfigProvider>
</StyledComponentsRegistry>
</StyledRegistry>
</AntdRegistry>
</body>
</html>
);

View File

@ -1,18 +1,18 @@
import React from 'react';
import { ConfigProvider } from 'antd';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import Link from 'next/link';
import { comfortaa, inter } from './fonts';
import StyledComponentsRegistry from '../lib/AntdRegistry';
import StyledRegistry from '../lib/StyleRegistry';
import theme from '../constants/theme';
import { BackButton } from '../components/view';
import { BackButton } from '../components/view/BackButton';
export default function NotFound() {
return (
<html className={`${comfortaa.variable} ${inter.variable}`}>
<body>
<AntdRegistry>
<StyledRegistry>
<StyledComponentsRegistry>
<ConfigProvider theme={theme}>
<div className="b-wrapper">
<div className="b-content">
@ -51,8 +51,8 @@ export default function NotFound() {
</footer>
</div>
</ConfigProvider>
</StyledComponentsRegistry>
</StyledRegistry>
</AntdRegistry>
</body>
</html>
);

View File

@ -7,6 +7,7 @@ import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
import { Link } from '../../navigation';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
import { deleteStorageKey } from '../../hooks/useLocalStorage';
import { i18nText } from '../../i18nKeys';
const Logout = styled(Button)`
width: 100%;
@ -19,7 +20,7 @@ const Logout = styled(Button)`
text-align: left !important;
`;
export const AccountMenu = ({ menu }: { menu: { path: string, title: string, count?: number }[] }) => {
export const AccountMenu = ({ menu, locale }: { menu: { path: string, title: string, count?: number }[], locale: string }) => {
const selectedLayoutSegment = useSelectedLayoutSegment();
const pathname = selectedLayoutSegment || '';
const paths = usePathname();
@ -51,7 +52,7 @@ export const AccountMenu = ({ menu }: { menu: { path: string, title: string, cou
type="link"
onClick={onLogout}
>
Log Out
{i18nText('logout', locale)}
</Logout>
</li>
<li className="list-sidebar__item">
@ -59,7 +60,7 @@ export const AccountMenu = ({ menu }: { menu: { path: string, title: string, cou
type="link"
onClick={onDeleteAccount}
>
Delete account
{i18nText('deleteAcc', locale)}
</Logout>
</li>
</ul>

View File

@ -6,33 +6,18 @@ import type { UploadFile, UploadProps } from 'antd';
import ImgCrop from 'antd-img-crop';
import { CameraOutlined } from '@ant-design/icons';
import { Link } from '../../navigation';
import { CustomInput } from '../view';
import { CustomInput } from '../view/CustomInput';
import { Profile } from '../../types/profile';
import { useProfileSettings } from '../../actions/hooks/useProfileSettings';
import { i18nText } from '../../i18nKeys';
type ProfileSettingsProps = {
locale: string;
photoDesc?: string;
placeholderName?: string;
placeholderSurname?: string;
placeholderBirthday?: string;
placeholderEmail?: string;
changePasswordLink?: string;
saveButton?: string;
};
// type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0];
export const ProfileSettings: FC<ProfileSettingsProps> = ({
locale,
photoDesc,
placeholderName,
placeholderSurname,
placeholderBirthday,
placeholderEmail,
changePasswordLink,
saveButton
}) => {
export const ProfileSettings: FC<ProfileSettingsProps> = ({ locale }) => {
const [form] = Form.useForm<Profile>();
const { profileSettings } = useProfileSettings(locale);
@ -66,13 +51,13 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({
return (
<Form form={form} className="form-settings">
<div className="user-avatar">
<div className="user-avatar__edit">
<div className="user-avatar__edit" style={profileSettings?.faceImageUrl ? { backgroundImage: `url(${profileSettings.faceImageUrl})` } : undefined}>
<input className="" type="file" id="input-file" />
<label htmlFor="input-file" className="form-label" />
</div>
<div className="user-avatar__text">{photoDesc}</div>
<div className="user-avatar__text">{i18nText('photoDesc', locale)}</div>
</div>
<ImgCrop rotationSlider>
{/* <ImgCrop rotationSlider>
<Upload
action="https://run.mocky.io/v3/435e224c-44fb-4773-9faf-380c5e6a2188"
fileList={fileList}
@ -81,33 +66,33 @@ export const ProfileSettings: FC<ProfileSettingsProps> = ({
>
<Button icon={<CameraOutlined />}>Click to Upload</Button>
</Upload>
</ImgCrop>
</ImgCrop> */}
<div className="form-field">
<Form.Item name="username">
<CustomInput placeholder={placeholderName} />
<CustomInput placeholder={i18nText('name', locale)} />
</Form.Item>
</div>
<div className="form-field">
<Form.Item name="surname">
<CustomInput placeholder={placeholderSurname} />
<CustomInput placeholder={i18nText('surname', locale)} />
</Form.Item>
</div>
{/* <div className="form-field">
<Form.Item name="birthday">
<CustomInput placeholder={placeholderBirthday} />
<CustomInput placeholder={i18nText('birthday', locale)} />
</Form.Item>
</div> */}
<div className="form-field">
<Form.Item name="login">
<CustomInput type="email" placeholder={placeholderEmail} />
<CustomInput type="email" placeholder="E-mail" />
</Form.Item>
</div>
<div className="form-link">
<Link href={'change-password' as any}>
{changePasswordLink}
{i18nText('changePass', locale)}
</Link>
</div>
<button className="btn-apply">{saveButton}</button>
<button className="btn-apply">{i18nText('save', locale)}</button>
</Form>
);
};

View File

@ -1,7 +1,7 @@
'use client';
import React, { useCallback, useEffect, useState } from 'react';
import styled from 'styled-components';
import { Empty, Space } from 'antd';
import dayjs from 'dayjs';
import 'dayjs/locale/ru';
import 'dayjs/locale/en';
@ -9,15 +9,14 @@ import 'dayjs/locale/de';
import 'dayjs/locale/it';
import 'dayjs/locale/fr';
import 'dayjs/locale/es';
import { CustomSelect, Loader } from '../view';
import { Loader } from '../view/Loader';
import { useLocalStorage } from '../../hooks/useLocalStorage';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
import { getRecentSessions, getRequestedSessions, getUpcomingSessions } from '../../actions/profile';
import { Session, Sessions, SessionType } from '../../types/sessions';
import { i18nText } from '../../i18nKeys';
const Tab = styled.div``;
export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string, string>, locale: string }) => {
export const SessionsTabs = ({ locale }: { locale: string }) => {
const [activeTab, setActiveTab] = useState<number>(0);
const [sort, setSort] = useState<string>();
const [sessions, setSessions] = useState<Sessions>();
@ -59,7 +58,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
const getChildren = (list?: Session[]) => (
<>
<div className="filter-session">
{/* <div className="filter-session">
<div className="filter-session__item">
<CustomSelect
label="Topic"
@ -73,7 +72,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
]}
/>
</div>
</div>
</div> */}
<div className="list-session">
{list && list?.length > 0 ? list?.map(({ id, scheduledStartAtUtc, scheduledEndAtUtc, title, coach, clients }) => {
const client = clients?.length ? clients[0] : null;
@ -94,7 +93,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
<div className="card-profile__header__title">{title}</div>
<div className={`card-profile__header__date${today ? ' chosen': ''}`}>
{today
? `Today ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`
? `${i18nText('today', locale)} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`
: `${startDate.format('D MMMM')} ${startDate.format('HH:mm')} - ${endDate.format('HH:mm')}`}
</div>
</div>
@ -103,7 +102,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
</div>
)
}) : (
<div>not found</div>
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</div>
</>
@ -114,7 +113,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
key: SessionType.UPCOMING,
label: (
<>
{intlConfig?.upcoming || 'Tab 1'}
{i18nText('session.upcoming', locale)}
{sessions?.upcoming && sessions?.upcoming?.length > 0 ? (<span className="count">{sessions?.upcoming.length}</span>) : null}
</>
),
@ -124,7 +123,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
key: SessionType.REQUESTED,
label: (
<>
{intlConfig?.requested || 'Tab 2'}
{i18nText('session.requested', locale)}
{sessions?.requested && sessions?.requested?.length > 0 ? (<span className="count">{sessions?.requested.length}</span>) : null}
</>
),
@ -132,7 +131,7 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
},
{
key: SessionType.RECENT,
label: intlConfig?.recent || 'Tab 3',
label: i18nText('session.recent', locale),
children: getChildren(sessions?.recent)
}
];
@ -145,13 +144,13 @@ export const SessionsTabs = ({ intlConfig, locale }: { intlConfig: Record<string
>
<div className="tabs-session">
{tabs.map((tab, index) => (
<Tab
<Space
key={index}
className={`tabs-session__item ${index === activeTab ? 'active' : ''}`}
onClick={() => setActiveTab(index)}
>
{tab.label}
</Tab>
</Space>
))}
</div>
{tabs[activeTab].children}

View File

@ -1,3 +1,5 @@
'use client'
export { AccountMenu } from './AccountMenu';
export { SessionsTabs } from './SessionsTabs';
export { ProfileSettings } from './ProfileSettings';

View File

@ -6,18 +6,18 @@ import debounce from 'lodash/debounce';
import { useRouter } from '../../navigation';
import { AdditionalFilter } from '../../types/experts';
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
import { CustomInput, CustomSelect } from '../view';
import { CustomInput } from '../view/CustomInput';
import { CustomSelect } from '../view/CustomSelect';
import { i18nText } from '../../i18nKeys';
type ExpertAdditionalFilterProps = {
searchPlaceholder: string;
sortLabel: string;
basePath: string;
locale: string;
};
export const ExpertsAdditionalFilter = ({
searchPlaceholder,
sortLabel,
basePath,
locale
}: ExpertAdditionalFilterProps) => {
const searchParams = useSearchParams();
const router = useRouter();
@ -65,7 +65,7 @@ export const ExpertsAdditionalFilter = ({
<div className="main-find__search">
<div className="main-find__search__input">
<CustomInput
placeholder={searchPlaceholder}
placeholder={i18nText('searchExpert', locale)}
defaultValue={filter?.text}
onChange={onChangeInput}
allowClear
@ -73,13 +73,13 @@ export const ExpertsAdditionalFilter = ({
</div>
<div className="main-find__search__sort">
<CustomSelect
label={sortLabel}
label={i18nText('sort', locale)}
value={filter?.coachSort}
onChange={onChangeSort}
options={[
// { value: 'byTop', label: 'By top views' },
{ value: 'byPriceAscending', label: 'By price ascending' },
{ value: 'byPriceDescending', label: 'By price descending' }
{ value: 'byPriceAscending', label: i18nText('sortPriceAsc', locale) },
{ value: 'byPriceDescending', label: i18nText('sortPriceDesc', locale) }
// { value: 'byRating', label: 'By rating' }
]}
/>

View File

@ -1,5 +1,4 @@
import React from 'react';
import { getTranslations } from 'next-intl/server';
import { DEFAULT_PAGE_SIZE } from '../../constants/common';
import { getFilter } from '../../utils/filter';
import { getTagList, getLanguages } from '../../actions/tags';
@ -7,6 +6,7 @@ import { getExpertsList } from '../../actions/experts';
import { ExpertsFilter } from './Filter';
import { ExpertsAdditionalFilter } from './AdditionalFilter';
import { ExpertsList } from './ExpertsList';
import { i18nText } from '../../i18nKeys';
type ExpertsProps = {
basePath?: string;
@ -15,11 +15,14 @@ type ExpertsProps = {
};
export const Experts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE }: ExpertsProps) => {
const t = await getTranslations('Experts');
const searchData = await getTagList(locale);
const languages = await getLanguages(locale);
const filter = getFilter({ pageSize });
const experts = await getExpertsList(filter, locale);
const durFrom = `${searchData?.sessionCostMin || 0}${locale === 'ru' ? 'мин' : 'min'}`;
const durTo = `${searchData?.sessionDurationMax || 0}${locale === 'ru' ? 'мин' : 'min'}`;
const priceFrom = `${searchData?.sessionCostMin || 0}`;
const priceTo = `${searchData?.sessionCostMax || 0}`;
return (
<div className="row">
@ -28,15 +31,14 @@ export const Experts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_
searchData={searchData}
languages={languages}
basePath={basePath}
priceTitle={t('filter.price', { from: searchData?.sessionCostMin || 0, to: searchData?.sessionCostMax || 0 })}
durationTitle={t('filter.duration', { from: searchData?.sessionDurationMin || 0, to: searchData?.sessionDurationMax || 0 })}
buttonApply={t('filter.apply')}
locale={locale}
priceTitle={i18nText('fromTo', locale).replace('$', priceFrom).replace('$', priceTo)}
durationTitle={i18nText('fromTo', locale).replace('$', durFrom).replace('$', durTo)}
/>
</div>
<div className="col-xl-9 col-lg-8 ">
<ExpertsAdditionalFilter
searchPlaceholder={t('filter.search')}
sortLabel={t('filter.sort')}
locale={locale}
basePath={basePath}
/>
<ExpertsList
@ -44,9 +46,6 @@ export const Experts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_
data={experts}
basePath={basePath}
baseFilter={filter}
priceTitle={t('list.price')}
durationTitle={t('list.duration')}
detailButton={t('list.details')}
pageSize={pageSize}
/>
</div>

View File

@ -10,13 +10,12 @@ import { Link, useRouter } from '../../navigation';
import { ExpertsData, Filter, GeneralFilter } from '../../types/experts';
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
import { getExpertsList } from '../../actions/experts';
import { CustomPagination, CustomSpin } from '../view';
import { CustomPagination } from '../view/CustomPagination';
import { CustomSpin } from '../view/CustomSpin';
import { i18nText } from '../../i18nKeys';
type ExpertListProps = {
data?: ExpertsData;
priceTitle: string;
durationTitle: string;
detailButton: string;
locale: string;
baseFilter: Filter;
pageSize: number;
@ -25,9 +24,6 @@ type ExpertListProps = {
export const ExpertsList = ({
data,
priceTitle,
durationTitle,
detailButton,
locale,
baseFilter,
pageSize,
@ -35,7 +31,8 @@ export const ExpertsList = ({
}: ExpertListProps) => {
const searchParams = useSearchParams();
const router = useRouter();
const getTitle = (str: string, value?: any): string => (value ? str.replace('0', value) : str);
const getDuration = (value?: any): string => `${value || 0}${locale === 'ru' ? 'мин' : 'min'}`;
const getPrice = (value?: any): string => `${value || 0}`;
const [experts, setExperts] = useState<ExpertsData | undefined>();
const [loading, setLoading] = useState<boolean>(true);
@ -105,7 +102,7 @@ export const ExpertsList = ({
<div className="card-profile__header__name">{`${item.name} ${item?.surname || ''}`}</div>
</Link>
<div className="card-profile__header__price">
{getTitle(priceTitle, item?.sessionCost)} <span>/ {getTitle(durationTitle, item?.sessionDuration)}</span>
{getPrice(item?.sessionCost)} <span>/ {getDuration(item?.sessionDuration)}</span>
</div>
</div>
<div className="card-profile__header__lang">
@ -134,7 +131,7 @@ export const ExpertsList = ({
<div className="card-profile__desc">{item?.description}</div>
<div className="card-profile__footer">
<Link href={`/experts/${item?.id}` as any}>
{detailButton}
{i18nText('details', locale)}
<RightOutlined style={{ fontSize: '10px', padding: '0 7px' }}/>
</Link>
</div>

View File

@ -8,7 +8,10 @@ import { useRouter } from '../../navigation';
import { Filter } from '../../types/experts';
import { Languages, SearchData, Tag } from '../../types/tags';
import { getObjectByFilter, getObjectByAdditionalFilter } from '../../utils/filter';
import { CustomSwitch, CustomSlider, CustomInput } from '../view';
import { CustomSwitch } from '../view/CustomSwitch';
import { CustomSlider } from '../view/CustomSlider';
import { CustomInput } from '../view/CustomInput';
import { i18nText } from '../../i18nKeys';
type ExpertsFilterProps = {
searchData?: SearchData;
@ -16,7 +19,7 @@ type ExpertsFilterProps = {
basePath: string;
priceTitle: string;
durationTitle: string;
buttonApply: string;
locale: string;
};
export const ExpertsFilter = ({
@ -25,7 +28,7 @@ export const ExpertsFilter = ({
basePath,
priceTitle,
durationTitle,
buttonApply
locale
}: ExpertsFilterProps) => {
const searchParams = useSearchParams();
const router = useRouter();
@ -161,7 +164,9 @@ export const ExpertsFilter = ({
const getLangList = () => {
const reg = searchLang ? new RegExp(searchLang, 'ig') : '';
const langList = reg ? (languages || []).filter(({ code, nativeSpelling }) => reg.test(code) || reg.test(nativeSpelling)) : languages;
return langList?.length && getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling })))
return langList?.length
? getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling })))
: null;
};
const getTagsList = () => {
@ -185,7 +190,7 @@ export const ExpertsFilter = ({
key: 'userLanguages',
label: (
<>
<div className="b-filter__collapsed__title">Session Language</div>
<div className="b-filter__collapsed__title">{i18nText('sessionLang', locale)}</div>
{!openedTabs.includes('userLanguages') && filter?.userLanguages?.length > 0 && (
<div className="b-filter__collapsed__desc">{getSelectedLanguage()}</div>
)}
@ -194,7 +199,7 @@ export const ExpertsFilter = ({
children: (
<>
<CustomInput
placeholder="Search"
placeholder={i18nText('search', locale)}
value={searchLang}
onChange={(e) => setSearchLang(e.target?.value)}
allowClear
@ -218,7 +223,7 @@ export const ExpertsFilter = ({
children: (
<>
<CustomInput
placeholder="Search"
placeholder={i18nText('search', locale)}
value={searchTags}
onChange={(e) => setSearchTags(e.target?.value)}
allowClear
@ -246,7 +251,7 @@ export const ExpertsFilter = ({
onChange={onChangeTab}
/>
<div className="b-filter__block">
<h3 className="title-h3">Price</h3>
<h3 className="title-h3">{i18nText('price', locale)}</h3>
<div className="b-filter__slider">
<CustomSlider
range
@ -260,7 +265,7 @@ export const ExpertsFilter = ({
<div className="b-filter__description">{priceTitle}</div>
</div>
<div className="b-filter__block">
<h3 className="title-h3">Duration</h3>
<h3 className="title-h3">{i18nText('duration', locale)}</h3>
<div className="b-filter__slider">
<CustomSlider
range
@ -277,7 +282,7 @@ export const ExpertsFilter = ({
className="btn-apply"
onClick={goToFilterPage}
>
{buttonApply}
{i18nText('apply', locale)}
</Button>
</div>
);

View File

@ -4,7 +4,11 @@ import Image from 'next/image';
import { AUTH_USER } from '../../../constants/common';
import { getAuth } from '../../../actions/auth';
import { getPersonalData } from '../../../actions/profile';
import { CustomInput, CustomInputPassword, FilledButton, OutlinedButton, LinkButton } from '../../view';
import { CustomInput } from '../../view/CustomInput';
import { CustomInputPassword } from '../../view/CustomInputPassword';
import { FilledButton } from '../../view/FilledButton';
import { OutlinedButton } from '../../view/OutlinedButton';
import { LinkButton } from '../../view/LinkButton';
type EnterProps = {
form: FormInstance;

View File

@ -1,5 +1,5 @@
import React from 'react';
import { FilledButton } from '../../view';
import { FilledButton } from '../../view/FilledButton';
export const FinishContent = () => (
<>

View File

@ -3,7 +3,10 @@ import { Form, FormInstance, notification } from 'antd';
import { AUTH_USER } from '../../../constants/common';
import { getRegister } from '../../../actions/auth';
import { setPersonData } from '../../../actions/profile';
import { CustomInput, CustomInputPassword, FilledButton, OutlinedButton } from '../../view';
import { CustomInput } from '../../view/CustomInput';
import { CustomInputPassword } from '../../view/CustomInputPassword';
import { FilledButton } from '../../view/FilledButton';
import { OutlinedButton } from '../../view/OutlinedButton';
type RegisterProps = {
form: FormInstance;

View File

@ -1,6 +1,8 @@
import React, { FC } from 'react';
import { Form, FormInstance } from 'antd';
import { CustomInput, FilledButton, LinkButton } from '../../view';
import { CustomInput } from '../../view/CustomInput';
import { FilledButton } from '../../view/FilledButton';
import { LinkButton } from '../../view/LinkButton';
type ResetProps = {
form: FormInstance;

View File

@ -2,8 +2,9 @@ import React from 'react';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { Link as IntlLink } from '../../../navigation';
import { i18nText } from '../../../i18nKeys';
export const Footer = () => {
export const Footer = ({ locale }: { locale: string }) => {
const t = useTranslations('Footer');
return (
@ -11,22 +12,22 @@ export const Footer = () => {
<div className="b-inner">
<div className="row">
<div className="col">
<a href="/" className="b-footer__logo">
<IntlLink href={'/' as any} className="b-footer__logo">
<img className="" src="/images/footer-logo.svg" alt=""/>
</a>
</IntlLink>
</div>
<div className="col-md-auto">
<div className="b-footer__row">
<div className="b-footer__coll">
<div className="b-footer__coll__item">
<a href={`mailto:${t('email')}`} className="b-footer__link">
<Link href={`mailto:${t('email')}`} className="b-footer__link">
<img className="" src="/images/mail-outline.svg" alt=""/>
{t('email')}
</a>
<a href="#" className="b-footer__link">
</Link>
<Link href="#" className="b-footer__link">
<img className="" src="/images/call-outline.svg" alt=""/>
{t('phone')}
</a>
</Link>
</div>
<span className="b-footer__text">
<img className="" src="/images/location-outline.svg" alt=""/>
@ -35,19 +36,23 @@ export const Footer = () => {
</div>
<div className="b-footer__coll-2">
<div className="b-footer__coll-2__item">
<IntlLink href={'/faq' as any} className="b-footer__link">{t('menu.faq')}</IntlLink>
<Link href={'/docs/BBUDDY_privacy_policy_fin.docx' as any} className="b-footer__link">{t(`menu.privacy-policy`)}</Link>
<IntlLink href={'/faq' as any} className="b-footer__link">
{i18nText('footer.faq', locale)}
</IntlLink>
<Link href={'/docs/BBUDDY_privacy_policy_fin.docx' as any} className="b-footer__link">
{i18nText('footer.policy', locale)}
</Link>
</div>
<div className="b-footer__social">
<a href="#" className="b-footer__link">
<Link href="#" className="b-footer__link">
<img className="" src="/images/logo-facebook.svg" alt=""/>
</a>
<a href="#" className="b-footer__link">
</Link>
<Link href="#" className="b-footer__link">
<img className="" src="/images/logo-instagram.svg" alt=""/>
</a>
<a href="#" className="b-footer__link">
</Link>
<Link href="#" className="b-footer__link">
<img className="" src="/images/logo-linkedin.svg" alt=""/>
</a>
</Link>
</div>
</div>
</div>

View File

@ -1,35 +1,21 @@
'use client';
'use client'
import React, { FC, useState, useEffect } from 'react';
import { Button } from 'antd';
import { useSelectedLayoutSegment } from 'next/navigation';
import { styled } from 'styled-components';
import { Link } from '../../../navigation';
import { AUTH_TOKEN_KEY } from '../../../constants/common';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { AuthModal } from '../../Modals/AuthModal';
import { i18nText } from '../../../i18nKeys';
type HeaderAuthLinksProps = {
enterTitle: string;
registerTitle: string;
accountTitle: string;
locale: string;
separatorClass?: string;
};
const LinkButton = styled(Button)`
color: #66A5AD !important;
font-size: 16px !important;
height: auto !important;
padding: 0 !important;
font-style: normal !important;
font-weight: 600 !important;
line-height: normal !important;
`;
export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
enterTitle,
registerTitle,
accountTitle,
locale,
separatorClass = 'b-header__nav__list__line'
}) => {
const [isOpenModal, setIsOpenModal] = useState<boolean>(false);
@ -52,29 +38,33 @@ export const HeaderAuthLinks: FC<HeaderAuthLinksProps> = ({
return token
? (
<li>
<Link href={'/account/sessions' as any} className={pathname === 'account' ? 'active' : ''}>{accountTitle}</Link>
<Link href={'/account/sessions' as any} className={pathname === 'account' ? 'active' : ''}>
{i18nText('account', locale)}
</Link>
</li>
)
: (
<>
<li>
<LinkButton
<Button
className="b-header__auth"
type="link"
onClick={() => onOpen('register')}
>
{registerTitle}
</LinkButton>
{i18nText('registration', locale)}
</Button>
</li>
<li>
<span className={separatorClass}>|</span>
</li>
<li>
<LinkButton
<Button
className="b-header__auth"
type="link"
onClick={() => onOpen('enter')}
>
{enterTitle}
</LinkButton>
{i18nText('enter', locale)}
</Button>
</li>
<AuthModal
open={isOpenModal}

View File

@ -6,16 +6,12 @@ import { Link } from '../../../navigation';
import { HeaderAuthLinks } from './HeaderAuthLinks';
type HeaderMenuProps = {
enterTitle: string;
registerTitle: string;
accountTitle: string;
locale: string;
linkConfig: { path: string, title: string }[];
};
export const HeaderMenu = ({
enterTitle,
registerTitle,
accountTitle,
locale,
linkConfig
}: HeaderMenuProps) => {
const selectedLayoutSegment = useSelectedLayoutSegment();
@ -30,7 +26,7 @@ export const HeaderMenu = ({
<Link href={`/${path}` as any} className={pathname === path ? 'active' : ''}>{title}</Link>
</li>
))}
<HeaderAuthLinks enterTitle={enterTitle} registerTitle={registerTitle} accountTitle={accountTitle} />
<HeaderAuthLinks locale={locale} />
</ul>
</nav>
</div>

View File

@ -6,17 +6,13 @@ import { HeaderAuthLinks } from './HeaderAuthLinks';
import { Link } from '../../../navigation';
type HeaderMenuMobileProps = {
locale: string;
linkConfig: { path: string, title: string }[];
enterTitle: string;
registerTitle: string;
accountTitle: string;
};
export const HeaderMobileMenu: FC<HeaderMenuMobileProps> = ({
linkConfig,
enterTitle,
registerTitle,
accountTitle
locale,
linkConfig
}) => {
const [showMobileMenu, setShowMobileMenu] = useState<boolean>(false);
const selectedLayoutSegment = useSelectedLayoutSegment();
@ -41,9 +37,7 @@ export const HeaderMobileMenu: FC<HeaderMenuMobileProps> = ({
<ul className="menu-mobile__header__nav">
<HeaderAuthLinks
separatorClass="menu-mobile__header__nav__line"
enterTitle={enterTitle}
registerTitle={registerTitle}
accountTitle={accountTitle}
locale={locale}
/>
</ul>
</div>

View File

@ -1,21 +1,19 @@
import React, { FC } from 'react';
import { useTranslations } from 'next-intl';
import React, { FC, Suspense } from 'react';
import { HeaderMenu } from './HeaderMenu';
import { LanguageSwitcher } from './LanguageSwitcher';
import { HeaderMobileMenu } from './HeaderMobileMenu';
import { HEAD_ROUTES } from '../../../constants/routes';
import { Link } from '../../../navigation';
import { i18nText } from '../../../i18nKeys';
type HeaderProps = {
locale: string;
};
export const Header: FC<HeaderProps> = ({ locale }) => {
const t = useTranslations('Header');
const routes: { path: string, title: string }[] = HEAD_ROUTES.map((item) => ({
path: item,
title: t(`menu.${item}`)
title: i18nText(`menu.${item}`, locale)
}));
return (
@ -29,21 +27,21 @@ export const Header: FC<HeaderProps> = ({ locale }) => {
alt=""
/>
</Link>
<Suspense>
<HeaderMenu
enterTitle={t('enter')}
registerTitle={t('registration')}
accountTitle={t('account')}
locale={locale}
linkConfig={routes}
/>
<LanguageSwitcher locale={locale} />
</Suspense>
</div>
</header>
<Suspense>
<HeaderMobileMenu
enterTitle={t('enter')}
registerTitle={t('registration')}
accountTitle={t('account')}
locale={locale}
linkConfig={routes}
/>
</Suspense>
</>
);
};

View File

@ -1,34 +1,4 @@
import React from 'react';
import styled from 'styled-components';
import { Input as AntdInput } from 'antd';
import { Input } from 'antd';
const Input = styled(AntdInput)`
padding: 15px 16px !important;
background: #F8F8F7 !important;
border: 1px solid #F8F8F7 !important;
border-radius: 8px !important;
color: #000 !important;
align-items: center;
input {
background-color: transparent !important;
}
&:focus, &:hover, &:focus-within {
border-color: #66A5AD !important;
box-shadow: none !important;
}
&::placeholder {
color: #000 !important;
opacity: .4 !important;
}
&.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless) {
border-color: #ff4d4f !important;
}
`;
export const CustomInput = (props: any) => (
<Input {...props} />
);
export const CustomInput = (props: any) => <Input rootClassName="b-input" {...props} />;

View File

@ -1,37 +1,10 @@
import React from 'react';
import styled from 'styled-components';
import { Input as AntdInput } from 'antd';
import { Input } 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;
}
`;
export const CustomInputPassword = (props: any) => (
<Input
<Input.Password
rootClassName="b-input-password"
iconRender={(visible) => (visible
? <EyeOutlined style={{ color: '#2C7873', fontSize: 20, opacity: .3 }} />
: <EyeInvisibleOutlined style={{ color: '#2C7873', fontSize: 20 }} />

View File

@ -1,71 +1,11 @@
'use client'
import React, {useEffect, useState} from 'react';
import styled from 'styled-components';
import { Select as AntdSelect, Tag } from 'antd';
import { Select, 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;
@ -89,9 +29,10 @@ export const CustomMultiSelect = (props: any) => {
}, [value]);
return (
<SelectWrap className={isActiveLabel ? 'b-multiselect__active' : ''}>
<SelectLabel>{label}</SelectLabel>
<div className={`b-multiselect-wrap ${isActiveLabel ? 'b-multiselect__active' : ''}`}>
<div className="b-multiselect-label">{label}</div>
<Select
className="b-multiselect"
mode="multiple"
value={value}
showSearch={false}
@ -101,6 +42,6 @@ export const CustomMultiSelect = (props: any) => {
onBlur={() => setIsActiveLabel(!!value?.length)}
{...other}
/>
</SelectWrap>
</div>
);
};

View File

@ -1,49 +1,5 @@
'use client';
import React from 'react';
import styled from 'styled-components';
import { Pagination as AntdPagination, PaginationProps } from 'antd';
const Pagination = styled(AntdPagination)`
display: flex;
gap: 8px;
.ant-pagination-item {
margin-inline-end: 0 !important;
height: 40px !important;
width: 40px !important;
border-radius: 8px !important;
line-height: 38px !important;
font-family: var(--font-inter) !important;
font-weight: 400 !important;
border-color: #2c7873 !important;
font-size: 16px !important;
a {
color: #2c7873 !important;
}
}
.ant-pagination-jump-next {
margin-inline-end: 0 !important;
height: 40px !important;
width: 40px !important;
line-height: 38px !important;
}
.ant-pagination-item-active {
background: #2c7873 !important;
a {
color: #fff !important;
}
}
.ant-pagination-item:not(.ant-pagination-item-active):active,
.ant-pagination-item:not(.ant-pagination-item-active):hover {
background-color: rgba(44, 120, 115, 0.06) !important;
}
`;
import { Pagination, PaginationProps } from 'antd';
const itemRender: PaginationProps['itemRender'] = (_, type, originalElement) => {
if (type === 'prev' || type === 'next') {
@ -55,6 +11,7 @@ const itemRender: PaginationProps['itemRender'] = (_, type, originalElement) =>
export const CustomPagination = (props: PaginationProps) => (
<Pagination
rootClassName="b-pagination"
itemRender={itemRender}
defaultCurrent={1}
showTitle={false}

View File

@ -1,25 +1,4 @@
import React from 'react';
import styled from 'styled-components';
import { Rate as AntdRate } from 'antd';
import { Rate } from 'antd';
const Rate = styled(AntdRate)`
display: inline-flex !important;
gap: 4px;
li {
margin-inline-end: 0 !important;
padding: 5px 0 0 !important;
&.ant-rate-star-full span {
color: #ffbd00 !important;
}
&.ant-rate-star-zero span {
color: #c4dfe6 !important;
}
}
`;
export const CustomRate = (props: any) => (
<Rate {...props} />
);
export const CustomRate = (props: any) => <Rate rootClassName="b-rate" {...props} />;

View File

@ -1,63 +1,7 @@
'use client'
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;
}
`;
import { Select } from 'antd';
export const CustomSelect = (props: any) => {
const { label, value, ...other } = props;
@ -72,14 +16,15 @@ export const CustomSelect = (props: any) => {
}, [value]);
return (
<SelectWrap className={isActiveLabel ? 'b-select__active' : ''}>
<SelectLabel>{label}</SelectLabel>
<div className={`b-select-wrap ${isActiveLabel ? 'b-select__active' : ''}`}>
<div className="b-select-label">{label}</div>
<Select
className="b-select"
value={value}
onFocus={!isActiveLabel ? () => setIsActiveLabel(true) : undefined}
onBlur={() => setIsActiveLabel(!!value)}
{...other}
/>
</SelectWrap>
</div>
);
};

View File

@ -1,49 +1,4 @@
'use client';
import React from 'react';
import styled from 'styled-components';
import { Slider as AntdSlider } from 'antd';
import { Slider } from 'antd';
const Slider = styled(AntdSlider)`
padding-block: 7px !important;
height: 16px !important;
.ant-slider-rail {
background-color: #E5E5E5 !important;
}
.ant-slider-track {
background-color: #66A5AD !important;
}
.ant-slider-handle {
width: 16px !important;
height: 16px !important;
&::before {
width: 16px !important;
height: 16px !important;
inset-inline-start: 0 !important;
inset-block-start: 0 !important;
}
&::after {
width: 16px !important;
height: 16px !important;
background-color: #66A5AD !important;
inset-inline-start: 0 !important;
inset-block-start: 0 !important;
box-shadow: none !important;
}
&:focus, &:hover {
&::after {
box-shadow: 0 0 0 12px rgba(102, 165, 173, .2) !important;
}
}
}
`;
export const CustomSlider = (props: any) => (
<Slider {...props} />
);
export const CustomSlider = (props: any) => <Slider className="b-slider" {...props} />;

View File

@ -1,12 +1,4 @@
import React from 'react';
import { Spin as AntdSpin } from 'antd';
import { styled } from 'styled-components';
import { Spin } from 'antd';
const Spin = styled(AntdSpin)`
width: 100%;
margin: 24px 0;
`;
export const CustomSpin = (props: any) => (
<Spin {...props} />
);
export const CustomSpin = (props: any) => <Spin className="b-spin" {...props} />;

View File

@ -1,34 +1,4 @@
'use client';
import React from 'react';
import styled from 'styled-components';
import { Switch as AntdSwitch } from 'antd';
import { Switch } from 'antd';
const Switch = styled(AntdSwitch)`
height: 24px !important;
background: #F8F8F7 !important;
.ant-switch-handle {
height: 24px !important;
width: 24px !important;
top: 0 !important;
&::before {
box-shadow: none !important;
background: #C4DFE6 !important;
border-radius: 50% !important;
}
}
&.ant-switch-checked {
background: #F8F8F7 !important;
.ant-switch-handle::before {
background: #66A5AD !important;
}
}
`;
export const CustomSwitch = (props: any) => (
<Switch {...props} />
);
export const CustomSwitch = (props: any) => <Switch className="b-switch" {...props} />;

View File

@ -1,17 +1,8 @@
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;
`;
import { Button } from 'antd';
export const FilledButton = (props: any) => (
<Button {...props}>
<Button className="b-button__filled" {...props}>
{props.children}
</Button>
);

View File

@ -1,16 +1,8 @@
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;
`;
import { Button } from 'antd';
export const LinkButton = (props: any) => (
<Button {...props}>
<Button className="b-button__link" {...props}>
{props.children}
</Button>
);

View File

@ -1,26 +1,8 @@
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;
}
`;
import { Button } from 'antd';
export const OutlinedButton = (props: any) => (
<Button {...props}>
<Button className="b-button__outlined" {...props}>
{props.children}
</Button>
);

View File

@ -1,17 +0,0 @@
'use client';
export * from './CustomSwitch';
export * from './CustomSlider';
export * from './CustomPagination';
export * from './CustomRate';
export * from './CustomInput';
export * from './CustomInputPassword';
export * from './CustomSelect';
export * from './CustomMultiSelect';
export * from './CustomSpin';
export * from './FilledButton';
export * from './OutlinedButton';
export * from './LinkButton';
export * from './BackButton';
export * from './WithError';
export * from './Loader';

58
src/i18nKeys/de.ts Normal file
View File

@ -0,0 +1,58 @@
export default {
accountMenu: {
sessions: 'Kommende & letzte Sitzungen',
notifications: 'Benachrichtigung',
support: 'Hilfe & Support',
information: 'Rechtliche Informationen',
settings: 'Profileinstellungen',
messages: 'Nachrichten',
'work-with-us': 'Arbeite mit uns'
},
menu: {
'bb-client': 'Mit BB wachsen',
'bb-expert': 'Werde BB-Experte',
blog: 'Blog&News'
},
registration: 'Registrieren',
enter: 'Anmelden',
account: 'Mein Konto',
logout: 'Abmelden',
deleteAcc: 'Konto löschen',
footer: {
faq: 'FAQ',
policy: 'Datenschutzrichtlinie'
},
session: {
upcoming: 'Kommende Sitzungen',
requested: 'Angefragte Sitzungen',
recent: 'Letzte Sitzungen'
},
photoDesc: 'Füge ein echtes Foto hinzu, mit Gesicht wirkt es immer glaubwürdiger.',
dayStart: 'Tagesbeginn',
topic: 'Thema',
name: 'Name',
surname: 'Nachname',
birthday: 'Geburtsdatum',
oldPass: 'Altes Passwort',
newPass: 'Neues Passwort',
confirmPass: 'Passwort bestätigen',
becomeExpert: '',
insertInfo: 'Füge deine persönlichen Informationen ein, um deine Reise als BBuddy-Experte zu beginnen',
changeUserData: 'Du kannst deine Angaben jederzeit ergänzen oder ändern\n',
price: 'Preis',
duration: 'Dauer',
search: 'Suche',
searchExpert: 'Nach einem Experten suchen',
sort: 'Sortieren',
sortPriceAsc: 'Nach Preis aufsteigend',
sortPriceDesc: 'Nach Preis absteigend',
details: 'Details',
sessionLang: 'Sitzungssprache',
fromTo: 'von $ bis $',
apply: 'Anwenden',
save: 'Speichern',
changePass: 'Passwort ändern',
getStarted: 'Loslegen',
delete: 'Löschen',
today: 'Heute'
}

View File

@ -7,5 +7,52 @@ export default {
settings: 'Profile Settings',
messages: 'Messages',
'work-with-us': 'Work With Us'
}
},
menu: {
'bb-client': 'Start grow with BB',
'bb-expert': 'Become BB Expert',
blog: 'Blog&News'
},
registration: 'Registration',
enter: 'Enter',
account: 'My Account',
logout: 'Log out',
deleteAcc: 'Delete account',
footer: {
faq: 'FAQ',
policy: 'Privacy Policy'
},
session: {
upcoming: 'Upcoming Sessions',
requested: 'Sessions Requested',
recent: 'Recent Sessions'
},
photoDesc: 'Add a real photo, as a person\'s face is always more credible.',
dayStart: 'Day start',
topic: 'Topic',
name: 'Name',
surname: 'Surname',
birthday: 'Date of Birth',
oldPass: 'Old Password',
newPass: 'New Password',
confirmPass: 'Confirm Password',
becomeExpert: '',
insertInfo: 'Insert your personal information to start your journey as a BBuddy Expert',
changeUserData: 'Your info can either be added or amended at anytime',
price: 'Price',
duration: 'Duration',
search: 'Search',
searchExpert: 'Search for an Expert',
sort: 'Sort',
sortPriceAsc: 'By price ascending',
sortPriceDesc: 'By price descending',
details: 'Details',
sessionLang: 'Session Language',
fromTo: 'from $ to $',
apply: 'Apply',
save: 'Save',
changePass: 'Change password',
getStarted: 'Get started',
delete: 'Delete',
today: 'Today'
}

58
src/i18nKeys/es.ts Normal file
View File

@ -0,0 +1,58 @@
export default {
accountMenu: {
sessions: 'Próximas y recientes sesiones',
notifications: 'Notificación',
support: 'Ayuda y asistencia',
information: 'Información jurídica',
settings: 'Ajustes del perfil',
messages: 'Mensajes',
'work-with-us': 'Trabaja con nosotros'
},
menu: {
'bb-client': 'Empieza a crecer con BB',
'bb-expert': 'Conviértete en un experto en BB',
blog: 'Blog y noticias'
},
registration: 'Registro',
enter: 'Entrar',
account: 'Mi cuenta',
logout: 'Cerrar sesión',
deleteAcc: 'Eliminar cuenta',
footer: {
faq: 'Preguntas frecuentes',
policy: 'Política de privacidad'
},
session: {
upcoming: 'Próximas sesiones',
requested: 'Sesiones solicitadas',
recent: 'Sesiones recientes'
},
photoDesc: 'Añade una foto real, ya que la cara de una persona siempre es más creíble.',
dayStart: 'Inicio del día',
topic: 'Tema',
name: 'Nombre',
surname: 'Apellido',
birthday: 'Fecha de nacimiento',
oldPass: 'Contraseña antigua',
newPass: 'Nueva contraseña',
confirmPass: 'Confirmar contraseña',
becomeExpert: '',
insertInfo: 'Introduce tu información personal para comenzar tu viaje como experto en BBuddy',
changeUserData: 'Tus datos pueden añadirse o modificarse en cualquier momento',
price: 'Precio',
duration: 'Duración',
search: 'Buscar',
searchExpert: 'Buscar un experto',
sort: 'Ordenar',
sortPriceAsc: 'Por precio ascendente',
sortPriceDesc: 'Por precio descendiente',
details: 'Detalles',
sessionLang: 'Idioma de la sesión',
fromTo: 'de $ a $',
apply: 'Solicitar',
save: 'Guardar',
changePass: 'Cambiar contraseña',
getStarted: 'Empieza',
delete: 'Eliminar',
today: 'Hoy día'
}

58
src/i18nKeys/fr.ts Normal file
View File

@ -0,0 +1,58 @@
export default {
accountMenu: {
sessions: 'Sessions futures et récentes',
notifications: 'Notification',
support: 'Aide et support',
information: 'Informations légales',
settings: 'Paramètres du profil',
messages: 'Messages',
'work-with-us': 'Travaillez avec nous'
},
menu: {
'bb-client': 'Commencez à vous développer avec BB',
'bb-expert': 'Devenez Expert BB',
blog: 'Blog et actus'
},
registration: 'Inscription',
enter: 'Saisir',
account: 'Mon compte',
logout: 'Déconnexion',
deleteAcc: 'Supprimer le compte',
footer: {
faq: 'FAQ',
policy: 'Politique de confidentialité'
},
session: {
upcoming: 'Prochaines sessions',
requested: 'Sessions demandées',
recent: 'Sessions récentes'
},
photoDesc: 'Ajoutez une photo réelle, le visage d\'une personne est toujours plus crédible.',
dayStart: 'Début de la journée',
topic: 'Sujet',
name: 'Prénom',
surname: 'Nom de famille',
birthday: 'Date de naissance',
oldPass: 'Ancien mot de passe',
newPass: 'Nouveau mot de passe',
confirmPass: 'Confirmer le mot de passe',
becomeExpert: '',
insertInfo: 'Insérez vos informations personnelles pour commencer votre voyage en tant qu\'expert BBuddy',
changeUserData: 'Vos informations peuvent être ajoutées ou modifiées à tout moment',
price: 'Prix',
duration: 'Durée',
search: 'Recherche',
searchExpert: 'Rechercher un expert',
sort: 'Trier',
sortPriceAsc: 'Par prix croissant',
sortPriceDesc: 'Par prix décroissant',
details: 'Détails',
sessionLang: 'Langue de la session',
fromTo: 'de $ à $',
apply: 'Appliquer',
save: 'Sauvegarder',
changePass: 'Modifier le mot de passe',
getStarted: 'Commencer',
delete: 'Supprimer',
today: 'Ce jour'
}

View File

@ -1,14 +1,18 @@
import { Locale } from '../types/locale';
import en from './en';
import ru from './ru';
import fr from './fr';
import de from './de';
import it from './it';
import es from './es';
const MESSAGES = {
[Locale.en]: en,
[Locale.ru]: ru,
[Locale.de]: en,
[Locale.fr]: en,
[Locale.it]: en,
[Locale.es]: en
[Locale.de]: de,
[Locale.fr]: fr,
[Locale.it]: it,
[Locale.es]: es
};
const getValue = (keys: string[], dictionary: any) => {

58
src/i18nKeys/it.ts Normal file
View File

@ -0,0 +1,58 @@
export default {
accountMenu: {
sessions: 'Prossime e recenti sessioni',
notifications: 'Notifica',
support: 'Assistenza e supporto',
information: 'Informazioni legali',
settings: 'Impostazioni profilo',
messages: 'Messaggi',
'work-with-us': 'Lavora con noi'
},
menu: {
'bb-client': 'Inizia a crescere con BB',
'bb-expert': 'Diventa esperto BB',
blog: 'Blog&Notizie'
},
registration: 'Registrazione',
enter: 'Inserisci',
account: 'Il mio account',
logout: 'Disconnetti',
deleteAcc: 'Elimina account',
footer: {
faq: 'Domande frequenti',
policy: 'Informativa sulla privacy'
},
session: {
upcoming: 'Prossime sessioni',
requested: 'Sessioni richieste',
recent: 'Sessioni recenti'
},
photoDesc: 'Aggiungi una foto vera: il volto di una persona è sempre più credibile.',
dayStart: 'Inizio del giorno',
topic: 'Argomento',
name: 'Nome',
surname: 'Cognome',
birthday: 'Data di nascita',
oldPass: 'Vecchia password',
newPass: 'Nuova password',
confirmPass: 'Conferma password',
becomeExpert: '',
insertInfo: 'Inserisci i tuoi dati personali per iniziare il tuo viaggio come esperto BBuddy',
changeUserData: 'I tuoi dati possono essere aggiunti o modificati in qualsiasi momento',
price: 'Prezzo',
duration: 'Durata',
search: 'Ricerca',
searchExpert: 'Cerca un Esperto',
sort: 'Ordina',
sortPriceAsc: 'Per prezzo crescente',
sortPriceDesc: 'Per prezzo decrescente',
details: 'Dettagli',
sessionLang: 'Lingua sessione',
fromTo: 'da $ a $',
apply: 'Applica',
save: 'Salva',
changePass: 'Cambia password',
getStarted: 'Inizia',
delete: 'Elimina',
today: 'Oggi'
}

View File

@ -1,11 +1,58 @@
export default {
accountMenu: {
sessions: 'Upcoming & Recent Sessions',
notifications: 'Notification',
support: 'Help & Support',
information: 'Legal Information',
settings: 'Profile Settings',
messages: 'Messages',
'work-with-us': 'Work With Us'
}
sessions: 'Предстоящие и недавние сессии',
notifications: 'Уведомления',
support: 'Служба поддержки',
information: 'Юридическая информация',
settings: 'Настройки профиля',
messages: 'Сообщения',
'work-with-us': 'Сотрудничество'
},
menu: {
'bb-client': 'Начните свой рост с BB',
'bb-expert': 'Станьте экспертом BB',
blog: 'Блог и новости'
},
registration: 'Регистрация',
enter: 'Войти',
account: 'Учетная запись',
logout: 'Выйти',
deleteAcc: 'Удалить учетную запись',
footer: {
faq: 'Частые вопросы',
policy: 'Политика конфиденциальности'
},
session: {
upcoming: 'Предстоящие сессии',
requested: 'Запрошенные сессии',
recent: 'Недавние сессии'
},
photoDesc: 'Добавьте реальную фотографию, ведь лицо человека всегда вызывает больше доверия.',
dayStart: 'День начала',
topic: 'Тема',
name: 'Имя',
surname: 'Фамилия',
birthday: 'Дата рождения',
oldPass: 'Старый пароль',
newPass: 'Новый пароль',
confirmPass: 'Подтвердите пароль',
becomeExpert: '',
insertInfo: 'Введите личные данные и начните свой путь эксперта BBuddy',
changeUserData: 'Добавить и изменить информацию о себе можно в любое время',
price: 'Цена',
duration: 'Длительность',
search: 'Поиск',
searchExpert: 'Поиск эксперта',
sort: 'Сортировать',
sortPriceAsc: 'Цена по возрастанию',
sortPriceDesc: 'Цена по убыванию',
details: 'Информация',
sessionLang: 'Язык сессии',
fromTo: 'от $ до $',
apply: 'Применить',
save: 'Сохранить',
changePass: 'Изменить пароль',
getStarted: 'Начать работу',
delete: 'Удалить',
today: 'Сегодня'
}

View File

@ -1,22 +0,0 @@
'use client';
import React from 'react';
import { createCache, extractStyle, StyleProvider } from '@ant-design/cssinjs';
import type Entity from '@ant-design/cssinjs/es/Cache';
import { useServerInsertedHTML } from 'next/navigation';
const StyledComponentsRegistry = ({ children }: React.PropsWithChildren) => {
const cache = React.useMemo<Entity>(() => createCache(), []);
const isServerInserted = React.useRef<boolean>(false);
useServerInsertedHTML(() => {
// avoid duplicate css insert
if (isServerInserted.current) {
return;
}
isServerInserted.current = true;
return <style id="antd" dangerouslySetInnerHTML={{ __html: extractStyle(cache, true) }} />;
});
return <StyleProvider cache={cache}>{children}</StyleProvider>;
};
export default StyledComponentsRegistry;

View File

@ -320,7 +320,7 @@ a {
margin-bottom: 16px;
}
.ant-collapse-item {
.ant-collapse-ghost >.ant-collapse-item, .ant-collapse-item {
border-bottom: 1px solid #C4DFE6;
border-radius: 0 !important;
}

View File

@ -109,6 +109,7 @@ textarea {
background-image: url(/images/user-avatar.png);
background-position: 50%;
background-repeat: no-repeat;
background-size: cover;
input {
width: 0.1px;

View File

@ -41,6 +41,16 @@
}
}
&__auth {
color: #66A5AD !important;
font-size: 16px !important;
height: auto !important;
padding: 0 !important;
font-style: normal !important;
font-weight: 600 !important;
line-height: normal !important;
}
&__nav {
display: flex;
align-items: center;

View File

@ -15,4 +15,8 @@
@import "_message.scss";
@import "_auth-modal.scss";
@import "./view/style.scss";

View File

@ -0,0 +1,33 @@
.b-button {
&__filled {
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;
}
&__link {
color: #66A5AD !important;
font-size: 15px !important;
height: auto !important;
padding: 0 !important;
}
&__outlined {
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;
}
}
}

View File

@ -0,0 +1,53 @@
.b-input {
padding: 15px 16px !important;
background: #F8F8F7 !important;
border: 1px solid #F8F8F7 !important;
border-radius: 8px !important;
color: #000 !important;
align-items: center;
input {
background-color: transparent !important;
}
&:focus, &:hover, &:focus-within {
border-color: #66A5AD !important;
box-shadow: none !important;
}
&::placeholder {
color: #000 !important;
opacity: .4 !important;
}
&.ant-input-status-error:not(.ant-input-disabled):not(.ant-input-borderless) {
border-color: #ff4d4f !important;
}
&-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;
}
}
}

View File

@ -0,0 +1,40 @@
.b-pagination {
display: flex;
gap: 8px;
.ant-pagination-item {
margin-inline-end: 0 !important;
height: 40px !important;
width: 40px !important;
border-radius: 8px !important;
line-height: 38px !important;
font-family: var(--font-inter) !important;
font-weight: 400 !important;
border-color: #2c7873 !important;
font-size: 16px !important;
a {
color: #2c7873 !important;
}
}
.ant-pagination-jump-next {
margin-inline-end: 0 !important;
height: 40px !important;
width: 40px !important;
line-height: 38px !important;
}
.ant-pagination-item-active {
background: #2c7873 !important;
a {
color: #fff !important;
}
}
.ant-pagination-item:not(.ant-pagination-item-active):active,
.ant-pagination-item:not(.ant-pagination-item-active):hover {
background-color: rgba(44, 120, 115, 0.06) !important;
}
}

View File

@ -0,0 +1,17 @@
.b-rate {
display: inline-flex !important;
gap: 4px;
li {
margin-inline-end: 0 !important;
padding: 5px 0 0 !important;
&.ant-rate-star-full span {
color: #ffbd00 !important;
}
&.ant-rate-star-zero span {
color: #c4dfe6 !important;
}
}
}

View File

@ -0,0 +1,116 @@
.b-multiselect {
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;
}
}
&-wrap {
position: relative;
width: 100%;
&.b-multiselect__active .b-multiselect-label {
font-size: 12px;
font-weight: 300;
line-height: 14px;
top: 8px;
}
}
&-label {
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 {
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;
}
}
&-wrap {
position: relative;
width: 100%;
&.b-select__active .b-select-label {
font-size: 12px;
font-weight: 300;
line-height: 14px;
top: 8px;
}
}
&-label {
font-size: 14px;
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;
}
}

View File

@ -0,0 +1,39 @@
.b-slider {
padding-block: 7px !important;
height: 16px !important;
.ant-slider-rail {
background-color: #E5E5E5 !important;
}
.ant-slider-track {
background-color: #66A5AD !important;
}
.ant-slider-handle {
width: 16px !important;
height: 16px !important;
&::before {
width: 16px !important;
height: 16px !important;
inset-inline-start: 0 !important;
inset-block-start: 0 !important;
}
&::after {
width: 16px !important;
height: 16px !important;
background-color: #66A5AD !important;
inset-inline-start: 0 !important;
inset-block-start: 0 !important;
box-shadow: none !important;
}
&:focus, &:hover {
&::after {
box-shadow: 0 0 0 12px rgba(102, 165, 173, .2) !important;
}
}
}
}

View File

@ -0,0 +1,4 @@
.b-spin {
width: 100%;
margin: 24px 0;
}

View File

@ -0,0 +1,24 @@
.b-switch {
height: 24px !important;
background: #F8F8F7 !important;
.ant-switch-handle {
height: 24px !important;
width: 24px !important;
top: 0 !important;
&::before {
box-shadow: none !important;
background: #C4DFE6 !important;
border-radius: 50% !important;
}
}
&.ant-switch-checked {
background: #F8F8F7 !important;
.ant-switch-handle::before {
background: #66A5AD !important;
}
}
}

View File

@ -0,0 +1,8 @@
@import "_input.scss";
@import "_select.scss";
@import "_pagination.scss";
@import "_rate.scss";
@import "_slider.scss";
@import "_spin.scss";
@import "_switch.scss";
@import "_buttons.scss";