Compare commits

...

15 Commits

Author SHA1 Message Date
SD 73229cbe19 0.3.5 2025-02-25 00:46:01 +04:00
dzfelix c89584dba0 fix chat join 2025-02-24 18:46:38 +03:00
dzfelix 83965dd675 fix chat 2025-02-24 18:17:56 +03:00
SD a96a49d649 0.3.4 2025-02-17 16:37:44 +04:00
SD f8c797abf3 feat: rollback dependencies 2025-02-17 16:37:28 +04:00
SD 6b8cad1081 0.3.3 2025-02-17 01:01:50 +04:00
SD ccc3e2cb79 feat: update i18n, next core 2025-02-17 01:01:39 +04:00
SD 9ecb7d6981 feat: update i18n, next core 2025-02-17 01:00:27 +04:00
SD fc743dbb7d 0.3.2 2025-02-14 16:00:14 +04:00
SD 28789e9054 feat: fix next public variable, add google verify 2025-02-14 15:59:26 +04:00
SD 4e849b47a2 0.3.1 2025-02-07 20:35:31 +04:00
SD 33bf78ecc3 fix oauth 2025-02-07 19:49:43 +04:00
SD 42a4ae0c6c feat: update google login 2025-01-31 00:33:41 +04:00
SD fdb464ae68 merge develop 2024-12-29 15:21:18 +04:00
norton81 6a9bed479a demo 2024-06-30 17:51:56 +03:00
73 changed files with 2909 additions and 1673 deletions

5
.env
View File

@ -1,8 +1,9 @@
NEXT_PUBLIC_SERVER_BASE_URL=https://api.bbuddy.expert/api
NEXT_PUBLIC_AGORA_APPID=ed90c9dc42634e5687d4e2e0766b363f
NEXT_PUBLIC_GOOGLE_CLIENT_ID=909563069647-03rivr8k1jmirf382bcfehegamthcfg4.apps.googleusercontent.com
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=pk_test_51LVB3LK5pVGxNPeKk4gedt5NW4cb8k7BVXvgOMPTK4x1nnbGTD8BCqDqgInboT6N72YwrTl4tOsVz8rAjbUadX1m00y4Aq5qE8
STRIPE_SECRET_KEY=sk_test_51LVB3LK5pVGxNPeK6j0wCsPqYMoGfcuwf1LpwGEBsr1dUx4NngukyjYL2oMZer5EOlW3lqnVEPjNDruN0OkUohIf00fWFUHN5O
STRIPE_PAYMENT_DESCRIPTION='BBuddy services'
NEXT_PUBLIC_STRIPE_SECRET_KEY=sk_test_51LVB3LK5pVGxNPeK6j0wCsPqYMoGfcuwf1LpwGEBsr1dUx4NngukyjYL2oMZer5EOlW3lqnVEPjNDruN0OkUohIf00fWFUHN5O
NEXT_PUBLIC_STRIPE_PAYMENT_DESCRIPTION='BBuddy services'
NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf
NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc

2
.gitignore vendored
View File

@ -38,3 +38,5 @@ yarn-error.log*
# typescript
*.tsbuildinfo
next-env.d.ts
certificates

View File

@ -1,5 +1,6 @@
// @ts-check
const withNextIntl = require('next-intl/plugin')();
const createNextIntlPlugin = require('next-intl/plugin');
const withNextIntl = createNextIntlPlugin();
const path = require('path');
const json = require('./package.json');

3771
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "bbuddy-ui",
"version": "0.3.0",
"version": "0.3.5",
"private": true,
"scripts": {
"dev": "next dev -p 4200",
@ -14,6 +14,7 @@
"@ant-design/nextjs-registry": "^1.0.0",
"@contentful/rich-text-react-renderer": "^15.22.9",
"@microsoft/signalr": "^8.0.7",
"@react-oauth/google": "^0.12.1",
"@stripe/react-stripe-js": "^2.7.3",
"@stripe/stripe-js": "^4.1.0",
"agora-rtc-react": "2.1.0",
@ -28,6 +29,7 @@
"next": "14.0.3",
"next-intl": "^3.3.1",
"react": "^18",
"react-apple-login": "^1.1.6",
"react-dom": "^18",
"react-signalr": "^0.2.24",
"react-slick": "^0.29.0",

View File

@ -12,3 +12,39 @@ export const getRegister = (locale: string): Promise<{ jwtToken: string }> => ap
method: 'post',
locale
});
export const getRegisterByGoogle = (locale: string, accesstoken: string): Promise<{ jwtToken: string }> => apiRequest({
url: '/auth/registerexternal',
method: 'post',
data: {
platform: 0,
provider: 4,
accesstoken
},
locale
});
export const getLoginByGoogle = (locale: string, accesstoken: string): Promise<{ jwtToken: string }> => apiRequest({
url: '/auth/tryloginexternal',
method: 'post',
data: {
platform: 0,
provider: 4,
accesstoken
},
locale
});
export const getRegisterByApple = (locale: string, code: string): Promise<{ jwtToken: string }> => apiRequest({
url: '/auth/registerexternalappleweb',
method: 'post',
data: { code },
locale
});
export const getLoginByApple = (locale: string, code: string): Promise<{ jwtToken: string }> => apiRequest({
url: '/auth/tryloginexternalappleweb',
method: 'post',
data: { code },
locale
});

View File

@ -23,20 +23,20 @@ export const useSessionTracking = (locale: string, sessionId: number) => {
useEffect(() => {
return () => {
window.clearInterval(timer.current);
window?.clearInterval(timer.current);
}
}, []);
const start = () => {
window.clearInterval(timer.current);
window?.clearInterval(timer.current);
timer.current = window.setInterval(() => {
timer.current = window?.setInterval(() => {
fetchData();
}, DURATION);
};
const stop = () => {
window.clearInterval(timer.current);
window?.clearInterval(timer.current);
};
return {

View File

@ -13,7 +13,6 @@ export async function createCheckoutSession(
const ui_mode = data.get(
"uiMode",
) as Stripe.Checkout.SessionCreateParams.UiMode;
console.log('DATA', data)
const origin: string = headers().get("origin") as string;
const checkoutSession: Stripe.Checkout.Session =

View File

@ -1,8 +1,8 @@
import React from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
export default function Directions({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
export default function Directions() {
// unstable_setRequestLocale(locale);
return (
<div className="main-popular">

View File

@ -1,10 +1,10 @@
import React from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { useTranslations } from 'next-intl';
import { Experts } from '../../../../components/Experts/Experts';
export default function ExpertsPage({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const t = useTranslations('Experts');
return (

View File

@ -1,12 +1,12 @@
import React from 'react';
// import { useTranslations } from 'next-intl';
import Link from 'next/link';
import { getTranslations, unstable_setRequestLocale } from 'next-intl/server';
import { getTranslations } from 'next-intl/server';
import { i18nText } from '../../../../i18nKeys';
import { fetchBlogPosts } from '../../../../lib/contentful/blogPosts';
export default async function News({params: {locale}}: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const t = await getTranslations('Main');
const { data, total } = await fetchBlogPosts({preview: false, sticky: true})

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { useTranslations } from 'next-intl';
import { i18nText } from '../../../../../i18nKeys';
@ -10,7 +10,7 @@ export const metadata: Metadata = {
};
export default function Information({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const t = useTranslations('Account.LegalInformation');
return (

View File

@ -1,6 +1,6 @@
'use client'
import React from 'react';
import { Link } from '../../../../../../navigation';
import { Link } from '../../../../../../i18n/routing';
import { i18nText } from '../../../../../../i18nKeys';
import {ChatMessages} from "../../../../../../components/Chat/ChatMessages";
/*

View File

@ -13,7 +13,7 @@ export default function Messages({ params: { locale } }: { params: { locale: st
<Suspense>
<CustomInput placeholder={i18nText('name', locale)} />
</Suspense>
<ChatList locale={locale}/>
<ChatList locale={locale}/>
</>
);
};

View File

@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import dayjs from 'dayjs';
import 'dayjs/locale/ru';
import 'dayjs/locale/en';
@ -16,7 +16,7 @@ export const metadata: Metadata = {
};
export default function Notifications({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const date = dayjs('2022-05-22').locale(locale);
return (

View File

@ -1,10 +1,10 @@
import React from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
import { Link } from '../../../../../../navigation';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { Link } from '../../../../../../i18n/routing';
import { i18nText } from '../../../../../../i18nKeys/';
export default function ChangePassword({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
return (
<>

View File

@ -1,10 +1,10 @@
import React, { Suspense } from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { ProfileSettings } from '../../../../../components/Account';
import { i18nText } from '../../../../../i18nKeys';
export default function Settings({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
return (
<>

View File

@ -1,5 +1,5 @@
import React from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import type { Metadata } from 'next';
import { i18nText } from '../../../../../i18nKeys';
@ -9,7 +9,7 @@ export const metadata: Metadata = {
};
export default function Support({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
return (
<>

View File

@ -1,5 +1,5 @@
import React, { Suspense } from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { AccountMenu, RoomDetails, RoomsTabs } from '../../../../../../components/Account';
import { RoomsType } from '../../../../../../types/rooms';
@ -13,7 +13,7 @@ export async function generateStaticParams({
}
export default function RoomsDetailItem({ params: { locale, slug } }: { params: { locale: string, slug?: string[] } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const roomType: string = slug?.length > 0 && slug[0] || '';
const roomId: number | null = slug?.length > 1 && Number(slug[1]) || null;

View File

@ -1,5 +1,5 @@
import React, { Suspense } from 'react';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { AccountMenu, SessionDetails, SessionsTabs } from '../../../../../../components/Account';
import { SessionType } from '../../../../../../types/sessions';
@ -13,7 +13,7 @@ export async function generateStaticParams({
}
export default function SessionDetailItem({ params: { locale, slug } }: { params: { locale: string, slug?: string[] } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const sessionType: string = slug?.length > 0 && slug[0] || '';
const sessionId: number | null = slug?.length > 1 && Number(slug[1]) || null;

View File

@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { useTranslations } from 'next-intl';
import { GeneralTopSection } from '../../../components/Page';
@ -9,8 +9,8 @@ export const metadata: Metadata = {
description: 'Bbuddy desc Take the lead with BB'
};
export default function BbClientPage({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
export default function BbClientPage() {
// unstable_setRequestLocale(locale);
const t = useTranslations('BbClient');
return (

View File

@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { useTranslations } from 'next-intl';
import { GeneralTopSection } from '../../../components/Page';
import { ScreenCarousel } from '../../../components/Page/ScreenCarousel';
@ -10,8 +10,8 @@ export const metadata: Metadata = {
description: 'Bbuddy desc Become a BB expert'
};
export default function BbExpertPage({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
export default function BbExpertPage() {
// unstable_setRequestLocale(locale);
const t = useTranslations('BbExpert');
return (

View File

@ -50,7 +50,6 @@ function renderWidget (widget: Widget, index: number) {
export default async function BlogItem({params}: { params: BlogPostPageParams }) {
const item = await fetchBlogPost({slug: params.slug, preview: draftMode().isEnabled })
console.log('BLOG POST')
console.log(Util.inspect(item, {showHidden: false, depth: null, colors: true}))
if (!item) notFound();

View File

@ -1,7 +1,7 @@
import React from 'react';
import type { Metadata } from 'next';
import { draftMode } from 'next/headers'
import {unstable_setRequestLocale} from "next-intl/server";
// import {unstable_setRequestLocale} from "next-intl/server";
import Link from "next/link";
import {fetchBlogPosts} from "../../../../../lib/contentful/blogPosts";
import {fetchBlogPostCategories} from "../../../../../lib/contentful/blogPostsCategories";
@ -20,9 +20,9 @@ interface BlogPostPageProps {
params: BlogPostPageParams
}
export default async function Blog({params, searchParams}: { params: BlogPostPageParams, searhParams?: {page: number} }) {
unstable_setRequestLocale(params.locale);
const page = searchParams.page || undefined
export default async function Blog({params, searchParams}: { params: BlogPostPageParams, searchParams?: {page: number} }) {
// unstable_setRequestLocale(params.locale);
const page = searchParams?.page || undefined
return (
<BlogPosts basePath={'/'+params.locale+'/blog/'} locale={params.locale} currentCat={params.slug} page={page}/>
);

View File

@ -2,7 +2,7 @@ import React from 'react';
import type { Metadata } from 'next';
import * as Util from "node:util";
import {fetchBlogPosts} from "../../../lib/contentful/blogPosts";
import {unstable_setRequestLocale} from "next-intl/server";
// import {unstable_setRequestLocale} from "next-intl/server";
import Link from "next/link";
import {fetchBlogPostCategories} from "../../../lib/contentful/blogPostsCategories";
import {CustomPagination} from "../../../components/view/CustomPagination";
@ -24,10 +24,10 @@ export async function generateStaticParams(): Promise<BlogPostPageParams[]> {
}
export default async function Blog({ params: { locale }, searchParams }: { params: { locale: string }, searhParams?: {page: number} }) {
unstable_setRequestLocale(locale);
export default async function Blog({ params: { locale }, searchParams }: { params: { locale: string }, searchParams?: {page: number} }) {
// unstable_setRequestLocale(locale);
const pageSize = DEFAULT_PAGE_SIZE
const page = searchParams.page || undefined
const page = searchParams?.page || undefined
// BlogPosts('/'+locale+'/blog/', locale, pageSize)
return (
@ -36,7 +36,6 @@ export default async function Blog({ params: { locale }, searchParams }: { param
locale={locale}
pageSize={pageSize}
page={page}
>
</BlogPosts>
/>
);
}

View File

@ -1,6 +1,6 @@
import React, { Suspense } from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { getExpertById, getExpertsList } from '../../../../actions/experts';
import {
@ -20,7 +20,7 @@ export const metadata: Metadata = {
export async function generateStaticParams({
params: { locale },
}: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const result: { locale: string, expertId: string }[] = [];
const experts = await getExpertsList(locale, { themesTagIds: [] });

View File

@ -1,6 +1,6 @@
import React from 'react';
import type { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
// import { unstable_setRequestLocale } from 'next-intl/server';
import { useTranslations } from 'next-intl';
import { Experts } from '../../../components/Experts/Experts';
@ -10,7 +10,7 @@ export const metadata: Metadata = {
};
export default function ExpertsPage({ params: { locale } }: { params: { locale: string } }) {
unstable_setRequestLocale(locale);
// unstable_setRequestLocale(locale);
const t = useTranslations('Experts');
return (

View File

@ -1,12 +1,15 @@
import React, { ReactNode, Suspense } from 'react';
import { Metadata } from 'next';
import { unstable_setRequestLocale } from 'next-intl/server';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { notFound } from 'next/navigation';
import { ConfigProvider } from 'antd';
import { AntdRegistry } from '@ant-design/nextjs-registry';
import { GoogleOAuthProvider } from '@react-oauth/google';
import theme from '../../constants/theme';
import { ALLOWED_LOCALES } from '../../constants/locale';
import { Header, Footer, AppConfig } from '../../components/Page';
import { routing } from '../../i18n/routing';
type LayoutProps = {
children: ReactNode;
@ -21,25 +24,31 @@ export const metadata: Metadata = {
title: 'Bbuddy'
};
export default function LocaleLayout({ children, params: { locale } }: LayoutProps) {
if (!ALLOWED_LOCALES.includes(locale as any)) notFound();
export default async function LocaleLayout({ children, params: { locale } }: LayoutProps) {
if (!routing.locales.includes(locale as any)) {
notFound();
}
unstable_setRequestLocale(locale);
const messages = await getMessages();
return (
<AntdRegistry>
<ConfigProvider theme={theme}>
<div className="b-wrapper">
<Suspense fallback={null}>
<AppConfig />
</Suspense>
<div className="b-content">
<Header locale={locale} />
{children}
</div>
<Footer locale={locale} />
</div>
</ConfigProvider>
</AntdRegistry>
<NextIntlClientProvider messages={messages}>
<AntdRegistry>
<GoogleOAuthProvider clientId={process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID || ''}>
<ConfigProvider theme={theme}>
<div className="b-wrapper">
<Suspense fallback={null}>
<AppConfig />
</Suspense>
<div className="b-content">
<Header locale={locale} />
{children}
</div>
<Footer locale={locale} />
</div>
</ConfigProvider>
</GoogleOAuthProvider>
</AntdRegistry>
</NextIntlClientProvider>
);
}

View File

@ -0,0 +1,50 @@
'use client'
import React, { useEffect } from 'react';
import { notification } from 'antd';
import { useSearchParams } from 'next/navigation';
import { CustomSpin } from '../../../../components/view/CustomSpin';
import { getLoginByApple } from '../../../../actions/auth';
import { getUserData } from '../../../../actions/profile';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../../constants/common';
import { useLocalStorage } from '../../../../hooks/useLocalStorage';
import { useRouter } from '../../../../i18n/routing';
export default function AppleLoginPage({ params: { locale } }: { params: { locale: string } }) {
const params = useSearchParams();
const router = useRouter();
const [, setToken] = useLocalStorage(AUTH_TOKEN_KEY, '');
useEffect(() => {
const code = params.get('code');
if (code) {
getLoginByApple(locale, code)
.then((data) => {
if (data.jwtToken) {
getUserData(locale, data.jwtToken)
.then((profile) => {
localStorage.setItem(AUTH_USER, JSON.stringify(profile));
setToken(data.jwtToken);
})
} else {
notification.error({
message: 'Error',
description: 'Access denied'
});
}
})
.catch((error) => {
const err = error?.message ? JSON.parse(error.message) : {};
notification.error({
message: 'Error',
description: err?.details?.errMessage || undefined
});
})
.finally(() => {
router.push('/');
});
}
}, [params]);
return <CustomSpin />;
}

View File

@ -0,0 +1,45 @@
'use client'
import React, { useEffect } from 'react';
import { notification } from 'antd';
import { useSearchParams } from 'next/navigation';
import { CustomSpin } from '../../../../components/view/CustomSpin';
import { getRegisterByApple } from '../../../../actions/auth';
import { getUserData } from '../../../../actions/profile';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../../constants/common';
import { useLocalStorage } from "../../../../hooks/useLocalStorage";
import { useRouter } from '../../../../i18n/routing';
export default function AppleRegisterPage({ params: { locale } }: { params: { locale: string } }) {
const params = useSearchParams();
const router = useRouter();
const [, setToken] = useLocalStorage(AUTH_TOKEN_KEY, '');
useEffect(() => {
const code = params.get('code');
if (code) {
getRegisterByApple(locale, code)
.then((data) => {
if (data.jwtToken) {
getUserData(locale, data.jwtToken)
.then((profile) => {
localStorage.setItem(AUTH_USER, JSON.stringify(profile));
setToken(data.jwtToken);
})
}
})
.catch((error) => {
const err = error?.message ? JSON.parse(error.message) : {};
notification.error({
message: 'Error',
description: err?.details?.errMessage || undefined
});
})
.finally(() => {
router.push('/');
});
}
}, [params]);
return <CustomSpin />;
}

View File

@ -10,7 +10,10 @@ type RootLayoutProps = {
export const metadata: Metadata = {
title: 'Bbuddy',
description: 'Bbuddy'
description: 'Bbuddy',
verification: {
google: 'UqmM7WbpuMetvvkMeyVRGKiSvXHEGyaaRuYbWxM-njs'
}
};
export default function RootLayout({ children, params: { locale } }: RootLayoutProps) {

View File

@ -1,27 +0,0 @@
import { fetchBlogPosts } from '../lib/contentful/blogPosts';
export default async function sitemap() {
const paths = [
{
url: process.env.NEXT_PUBLIC_HOST,
lastModified: new Date(),
changeFrequency: "monthly",
priority: 1
}
]
const blogPosts = await fetchBlogPosts({ preview: false })
blogPosts.data.forEach((item) => {
paths.push({
url: `${process.env.NEXT_PUBLIC_HOST}${item.slug}`,
lastModified: item.createdAt.split('T')[0],
changeFrequency: 'daily',
priority: '1.0'
})
})
return paths
}

View File

@ -1,14 +1,14 @@
'use client';
import { useEffect, useState } from 'react';
import { Button } from 'antd';
import { useSelectedLayoutSegment, usePathname } from 'next/navigation';
import { Link } from '../../navigation';
import { Link } from '../../i18n/routing';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../constants/common';
import {deleteStorageKey, useLocalStorage} from '../../hooks/useLocalStorage';
import { i18nText } from '../../i18nKeys';
import { getMenuConfig } from '../../utils/account';
import {useEffect, useState} from "react";
import {getChatList} from "../../actions/chat/groups";
import { getChatList } from '../../actions/chat/groups';
export const AccountMenu = ({ locale }: { locale: string }) => {
const selectedLayoutSegment = useSelectedLayoutSegment();

View File

@ -5,7 +5,7 @@ import { Form, message, Upload } from 'antd';
import type { UploadFile } from 'antd';
import ImgCrop from 'antd-img-crop';
import { CameraOutlined, DeleteOutlined } from '@ant-design/icons';
import { useRouter } from '../../navigation';
import { useRouter } from '../../i18n/routing';
import { i18nText } from '../../i18nKeys';
import { ProfileRequest } from '../../types/profile';
import { validateImage } from '../../utils/account';

View File

@ -5,7 +5,7 @@ import { EditRoomForm } from './EditRoomForm';
import debounce from 'lodash/debounce';
import { createRoom } from '../../../actions/rooms';
import { Loader } from '../../view/Loader';
import { useRouter } from '../../../navigation';
import { useRouter } from '../../../i18n/routing';
import { RoomsType } from '../../../types/rooms';

View File

@ -5,7 +5,7 @@ import { Button, notification, Tag } from 'antd';
import { DeleteOutlined, LeftOutlined } from '@ant-design/icons';
import dayjs from 'dayjs';
import Image from 'next/image';
import { useRouter } from '../../../navigation';
import { useRouter } from '../../../i18n/routing';
import { Report, Room, RoomsType } from '../../../types/rooms';
import { i18nText } from '../../../i18nKeys';
import { LinkButton } from '../../view/LinkButton';

View File

@ -14,7 +14,7 @@ import { getRecentRooms, getUpcomingRooms } from '../../../actions/rooms';
import { Loader } from '../../view/Loader';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { AUTH_TOKEN_KEY } from '../../../constants/common';
import { usePathname, useRouter } from '../../../navigation';
import { usePathname, useRouter } from '../../../i18n/routing';
import { i18nText } from '../../../i18nKeys';
import { CreateRoom } from './CreateRoom';

View File

@ -5,7 +5,7 @@ import { Button, Empty, notification, Tag } from 'antd';
import { LeftOutlined, PlusOutlined, RightOutlined } from '@ant-design/icons';
import Image from 'next/image';
import dayjs from 'dayjs';
import { Link, useRouter } from '../../../navigation';
import { Link, useRouter } from '../../../i18n/routing';
import { i18nText } from '../../../i18nKeys';
import { getDuration, getPrice } from '../../../utils/expert';
import { PublicUser, Session, SessionState, SessionType } from '../../../types/sessions';

View File

@ -14,7 +14,7 @@ import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { AUTH_TOKEN_KEY, AUTH_USER } from '../../../constants/common';
import { getRecentSessions, getRequestedSessions, getUpcomingSessions } from '../../../actions/sessions';
import { Session, Sessions, SessionType } from '../../../types/sessions';
import { useRouter, usePathname } from '../../../navigation';
import { useRouter, usePathname } from '../../../i18n/routing';
import { i18nText } from '../../../i18nKeys';
type SessionsTabsProps = {

View File

@ -10,7 +10,7 @@ type PostsProps = {
basePath: string;
locale: string;
pageSize?: number;
currentCat: string;
currentCat?: string;
page?: number
};

View File

@ -3,7 +3,7 @@ import React, {useEffect, useState} from 'react';
import {AUTH_TOKEN_KEY} from '../../constants/common';
import {getChatList, getChatMessages} from "../../actions/chat/groups";
import {useLocalStorage} from "../../hooks/useLocalStorage";
import {Link} from "../../navigation";
import { Link } from "../../i18n/routing";
import dayjs from "dayjs";
import relativeTime from "dayjs/plugin/relativeTime";
import {message} from "antd";

View File

@ -19,8 +19,8 @@ type CompProps = {
};
export const ChatMessages = ({ locale, groupId }: CompProps) => {
const { newMessage, joinChat, readMessages, addListener } = SignalrConnection();
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
const { newMessage, joinChat, readMessages, addListener } = SignalrConnection({ jwt }) || {};
//const messages = await getChatMessages(locale, jwt, groupId)
const [loading, setLoading] = useState<boolean>(false);
const [text, setText] = useState('');
@ -46,6 +46,7 @@ export const ChatMessages = ({ locale, groupId }: CompProps) => {
setNotMe(_group.group.members[0].user)
}
setMessages(_messages.messages);
joinChat(groupId)
})
.catch((e) => {
@ -60,7 +61,7 @@ export const ChatMessages = ({ locale, groupId }: CompProps) => {
const onConnected = (flag: boolean) =>{
if (flag) {
if (flag && joinChat) {
joinChat(groupId)
readUreaded()
}
@ -73,7 +74,7 @@ export const ChatMessages = ({ locale, groupId }: CompProps) => {
msgs.push(message.id)
}
})
readMessages(msgs)
readMessages && readMessages(msgs);
}
const onReceiveMessage = (payload: any) => {
@ -103,7 +104,7 @@ export const ChatMessages = ({ locale, groupId }: CompProps) => {
} else {
setMessages([msg, ..._messages]);
}
readMessages([msg.id])
readMessages && readMessages([msg.id]);
}
@ -120,16 +121,18 @@ export const ChatMessages = ({ locale, groupId }: CompProps) => {
}
useEffect(() => {
addListener('onConnected', onConnected)
addListener('ReceiveMessage', onReceiveMessage)
addListener('MessageWasRead', onOpponentRead)
if (addListener) {
addListener('onConnected', onConnected);
addListener('ReceiveMessage', onReceiveMessage);
addListener('MessageWasRead', onOpponentRead);
}
}, [messages, me, setMessages]);
const handleSendMessages = () => {
newMessage({
newMessage && newMessage({
GroupId: groupId.toString(),
CreatorId: me.id,
Content: text

View File

@ -3,7 +3,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'next/navigation';
import debounce from 'lodash/debounce';
import { useRouter } from '../../navigation';
import { useRouter } from '../../i18n/routing';
import { AdditionalFilter } from '../../types/experts';
import { getObjectByFilter, getObjectByAdditionalFilter, getSearchParamsString } from '../../utils/filter';
import { CustomInput } from '../view/CustomInput';

View File

@ -16,7 +16,7 @@ import { AUTH_TOKEN_KEY, SESSION_DATA } from '../../constants/common';
import { ScheduleModal } from '../Modals/ScheduleModal';
import { ScheduleModalResult } from '../Modals/ScheduleModalResult';
import SignalrConnection from '../../lib/signalr-connection';
import { useRouter } from '../../navigation';
import { useRouter } from '../../i18n/routing';
import { useLocalStorage } from '../../hooks/useLocalStorage';
type ExpertDetailsProps = {
@ -38,7 +38,7 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
const isRus = locale === Locale.ru;
const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] , id, botUserId} } = expert || {};
const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, '');
const { joinChatPerson, closeConnection } = SignalrConnection();
const { joinChatPerson, closeConnection } = SignalrConnection({ jwt }) || {};
const router = useRouter();
@ -46,13 +46,13 @@ export const ExpertCard: FC<ExpertDetailsProps> = ({ expert, locale, expertId })
document?.addEventListener('show_pay_form', handleShowPayForm);
return () => {
closeConnection();
// if (closeConnection) closeConnection();
document?.removeEventListener('show_pay_form', handleShowPayForm);
}
}, []);
const handleJoinChat = (id?: number) => {
if (id) {
if (id && joinChatPerson) {
joinChatPerson(id).then((res: any) => {
router.push(`/account/messages/${res.id}` as string);
})

View File

@ -6,7 +6,7 @@ import { List, Tag } from 'antd';
import { RightOutlined } from '@ant-design/icons';
import isEqual from 'lodash/isEqual';
import Image from 'next/image';
import { Link, useRouter } from '../../navigation';
import { Link, useRouter } from '../../i18n/routing';
import { ExpertsData, Filter, GeneralFilter } from '../../types/experts';
import { getObjectByFilter, getObjectByAdditionalFilter, getSearchParamsString } from '../../utils/filter';
import { getDuration, getPrice } from '../../utils/expert';

View File

@ -4,7 +4,7 @@ import React, { useCallback, useEffect, useState } from 'react';
import { Button, Collapse, List } from 'antd';
import type { CollapseProps } from 'antd';
import { useSearchParams } from 'next/navigation';
import { useRouter } from '../../navigation';
import { useRouter } from '../../i18n/routing';
import { Filter } from '../../types/experts';
import { Languages, SearchData, Tag } from '../../types/tags';
import { getObjectByFilter, getObjectByAdditionalFilter, getSearchParamsString } from '../../utils/filter';

View File

@ -1,12 +1,16 @@
'use client';
import React, { Dispatch, FC, SetStateAction, useEffect } from 'react';
import { usePathname } from 'next/navigation';
import { usePathname, useSearchParams } from 'next/navigation';
import Link from 'next/link';
import { Modal, Form } from 'antd';
import { Modal, Form, notification } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent';
import { i18nText } from '../../i18nKeys';
import { useRouter } from '../../i18n/routing';
import { AUTH_USER} from '../../constants/common';
import { getRegisterByApple, getLoginByApple } from '../../actions/auth';
import { getUserData } from '../../actions/profile';
type AuthModalProps = {
open: boolean;
@ -27,6 +31,47 @@ export const AuthModal: FC<AuthModalProps> = ({
}) => {
const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>();
const paths = usePathname().split('/');
const params = useSearchParams();
const router = useRouter();
const onUpdateToken = (token: string) => {
if (updateToken && typeof updateToken !== 'string') {
updateToken(token);
}
};
useEffect(() => {
const code = params.get('code');
const type = params.get('state');
if (code && type) {
const appleFunc = type === 'bbregister' ? getRegisterByApple : getLoginByApple;
appleFunc(locale, code)
.then((data) => {
if (data.jwtToken) {
getUserData(locale, data.jwtToken)
.then((profile) => {
localStorage.setItem(AUTH_USER, JSON.stringify(profile));
onUpdateToken(data.jwtToken);
})
} else {
notification.error({
message: 'Error',
description: 'Access denied'
});
}
})
.catch((error) => {
const err = error?.message ? JSON.parse(error.message) : {};
notification.error({
message: 'Error',
description: err?.details?.errMessage || undefined
});
})
.finally(() => {
router.push('/');
});
}
}, [params]);
const onAfterClose = () => {
form.resetFields();
@ -38,12 +83,6 @@ export const AuthModal: FC<AuthModalProps> = ({
}
}, [mode]);
const onUpdateToken = (token: string) => {
if (updateToken && typeof updateToken !== 'string') {
updateToken(token);
}
};
return (
<Modal
className="b-modal"

View File

@ -76,7 +76,6 @@ export const ScheduleModal: FC<ScheduleModalProps> = ({
getSchedulerSession(parseData as SignupSessionData, locale || 'en', jwt)
.then((session) => {
setSessionId(session?.sessionId);
console.log(session?.sessionId);
})
.catch((err) => {
console.log(err);

View File

@ -3,7 +3,8 @@
import React, { useEffect, useState } from 'react';
import { Modal, Result } from 'antd';
import { CloseOutlined } from '@ant-design/icons';
import { useSearchParams, useRouter } from 'next/navigation';
import { useSearchParams } from 'next/navigation';
import { useRouter } from '../../i18n/routing';
import { Stripe } from 'stripe';
import { getStripePaymentStatus } from '../../actions/stripe';
import { sessionPaymentConfirm } from '../../actions/sessions';

View File

@ -1,12 +1,11 @@
import React, { FC, useState } from 'react';
import { Form, FormInstance, notification } from 'antd';
import Image from 'next/image';
import { Social } from '../../../types/social';
import { useGoogleLogin } from '@react-oauth/google';
import AppleLogin from 'react-apple-login';
import { AUTH_USER } from '../../../constants/common';
import { SocialConfig } from '../../../constants/social';
import { useOauthWindow } from '../../../hooks/useOauthWindow';
import { getAuth } from '../../../actions/auth';
import {getPersonalData, getUserData} from '../../../actions/profile';
import { getAuth, getLoginByGoogle } from '../../../actions/auth';
import { getUserData } from '../../../actions/profile';
import { CustomInput } from '../../view/CustomInput';
import { CustomInputPassword } from '../../view/CustomInputPassword';
import { FilledButton } from '../../view/FilledButton';
@ -30,7 +29,6 @@ export const EnterContent: FC<EnterProps> = ({
handleCancel
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const { openOauthWindow } = useOauthWindow();
const onLogin = () => {
form.validateFields().then(() => {
@ -59,47 +57,44 @@ export const EnterContent: FC<EnterProps> = ({
});
};
const onSocialEnter = (type: Social) => {
const url = SocialConfig[type].oauthUrl;
if (!url) return;
openOauthWindow(url, type, async (event: MessageEvent) => {
const { data: socialData } = event
// примерная схема последующей обработки
// const socialErrors: string[] = [];
// try {
// // отправляем запрос на бэк с данными из соц сети
// const { data: { jwtToken } } = await query(socialData);
// // обновляем токен
// updateToken(jwtToken);
// // получаем данные о пользователе
// await getAuthUser()
// } catch (error: any) {
// if (error.httpStatus === 449) {
// // ошибка, когда отсутствует e-mail
//
// // какие-то дальнейшие действия после получения ошибки, например, закрываем окно и открываем модалку регистрации
// handleCancel();
// openSocialEmailRequestModal(socialData);
// } else if (error.httpStatus === 409) {
// // ошибка, когда по переданному email уже существует аккаунт
//
// // какие-то дальнейшие действия после получения ошибки, например, закрываем окно и открываем модалку с вводом пароля
// handleCancel();
// openSocialPasswordModal(socialData);
// } else {
// // в остальных случаях записываем ошибку в массив ошибок
// socialErrors.push(error.toString());
// }
// }
//
// // если все успешно, закрываем окно
// handleCancel();
})
};
const onGoogleLogin = useGoogleLogin({
onError: (err) => {
notification.error({
message: err.error,
description: err.error_description
});
},
onSuccess: (tokenResponse) => {
setIsLoading(true);
getLoginByGoogle(locale, tokenResponse.access_token)
.then((data) => {
if (data.jwtToken) {
getUserData(locale, data.jwtToken)
.then((profile) => {
localStorage.setItem(AUTH_USER, JSON.stringify(profile));
updateToken(data.jwtToken);
handleCancel();
})
} else {
notification.error({
message: 'Error',
description: 'Access denied'
});
}
})
.catch((error) => {
const err = error?.message ? JSON.parse(error.message) : {};
notification.error({
message: 'Error',
description: err?.details?.errMessage || undefined
});
})
.finally(() => {
setIsLoading(false);
});
}
});
return (
<>
@ -155,21 +150,24 @@ export const EnterContent: FC<EnterProps> = ({
{`${i18nText('forgotPass', locale)}?`}
</LinkButton>
<span>{i18nText('or', locale)}</span>
<OutlinedButton
icon={<Image src="/images/facebook-logo.png" height={20} width={20} alt="" />}
onClick={() => onSocialEnter(Social.FACEBOOK)}
>
{i18nText('facebook', locale)}
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
onClick={() => onSocialEnter(Social.APPLE)}
>
{i18nText('apple', locale)}
</OutlinedButton>
<AppleLogin
clientId="bbuddy.expert"
redirectURI="https://bbuddy.expert"
state="bblogin"
responseType="code"
responseMode="query"
render={({ onClick }) => (
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
onClick={onClick}
>
{i18nText('apple', locale)}
</OutlinedButton>
)}
/>
<OutlinedButton
icon={<Image src="/images/google-logo.png" height={20} width={20} alt="" />}
onClick={() => onSocialEnter(Social.GOOGLE)}
onClick={onGoogleLogin}
>
{i18nText('google', locale)}
</OutlinedButton>

View File

@ -1,12 +1,11 @@
import React, { FC, useState } from 'react';
import { Form, FormInstance, notification } from 'antd';
import Image from 'next/image';
import { Social } from '../../../types/social';
import { useGoogleLogin } from '@react-oauth/google';
import AppleLogin from 'react-apple-login';
import { AUTH_USER } from '../../../constants/common';
import { SocialConfig } from '../../../constants/social';
import { getRegister } from '../../../actions/auth';
import { setPersonData } from '../../../actions/profile';
import { useOauthWindow } from '../../../hooks/useOauthWindow';
import { getRegister, getRegisterByGoogle } from '../../../actions/auth';
import { getUserData, setPersonData } from '../../../actions/profile';
import { CustomInput } from '../../view/CustomInput';
import { CustomInputPassword } from '../../view/CustomInputPassword';
import { FilledButton } from '../../view/FilledButton';
@ -29,7 +28,6 @@ export const RegisterContent: FC<RegisterProps> = ({
handleCancel
}) => {
const [isLoading, setIsLoading] = useState<boolean>(false);
const { openOauthWindow } = useOauthWindow();
const onRegister = () => {
form.validateFields().then(() => {
@ -64,47 +62,38 @@ export const RegisterContent: FC<RegisterProps> = ({
});
};
const onSocialRegister = (type: Social) => {
const url = SocialConfig[type].oauthUrl;
if (!url) return;
openOauthWindow(url, type, async (event: MessageEvent) => {
const { data: socialData } = event
// примерная схема последующей обработки
// const socialErrors: string[] = [];
// try {
// // отправляем запрос на бэк с данными из соц сети
// const { data: { jwtToken } } = await query(socialData);
// // обновляем токен
// updateToken(jwtToken);
// // получаем данные о пользователе
// await getAuthUser()
// } catch (error: any) {
// if (error.httpStatus === 449) {
// // ошибка, когда отсутствует e-mail
//
// // какие-то дальнейшие действия после получения ошибки, например, закрываем окно и открываем модалку регистрации
// handleCancel();
// openSocialEmailRequestModal(socialData);
// } else if (error.httpStatus === 409) {
// // ошибка, когда по переданному email уже существует аккаунт
//
// // какие-то дальнейшие действия после получения ошибки, например, закрываем окно и открываем модалку с вводом пароля
// handleCancel();
// openSocialPasswordModal(socialData);
// } else {
// // в остальных случаях записываем ошибку в массив ошибок
// socialErrors.push(error.toString());
// }
// }
//
// // если все успешно, закрываем окно
// handleCancel();
})
};
const onGoogleLogin = useGoogleLogin({
onError: (err) => {
notification.error({
message: err.error,
description: err.error_description
});
},
onSuccess: (tokenResponse) => {
setIsLoading(true);
getRegisterByGoogle(locale, tokenResponse.access_token)
.then((data) => {
if (data.jwtToken) {
getUserData(locale, data.jwtToken)
.then((profile) => {
localStorage.setItem(AUTH_USER, JSON.stringify(profile));
updateToken(data.jwtToken);
handleCancel();
})
}
})
.catch((error) => {
const err = error?.message ? JSON.parse(error.message) : {};
notification.error({
message: 'Error',
description: err?.details?.errMessage || undefined
});
})
.finally(() => {
setIsLoading(false);
});
}
});
return (
<>
@ -177,21 +166,24 @@ export const RegisterContent: FC<RegisterProps> = ({
</FilledButton>
<OutlinedButton onClick={() => updateMode('enter')}>{i18nText('enter', locale)}</OutlinedButton>
<span>{i18nText('or', locale)}</span>
<OutlinedButton
icon={<Image src="/images/facebook-logo.png" height={20} width={20} alt="" />}
onClick={() => onSocialRegister(Social.FACEBOOK)}
>
{i18nText('facebook', locale)}
</OutlinedButton>
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
onClick={() => onSocialRegister(Social.APPLE)}
>
{i18nText('apple', locale)}
</OutlinedButton>
<AppleLogin
clientId="bbuddy.expert"
redirectURI="https://bbuddy.expert"
state="bbregister"
responseType="code"
responseMode="query"
render={({ onClick }) => (
<OutlinedButton
icon={<Image src="/images/apple-logo.png" height={22} width={22} alt="" />}
onClick={onClick}
>
{i18nText('apple', locale)}
</OutlinedButton>
)}
/>
<OutlinedButton
icon={<Image src="/images/google-logo.png" height={20} width={20} alt="" />}
onClick={() => onSocialRegister(Social.GOOGLE)}
onClick={onGoogleLogin}
>
{i18nText('google', locale)}
</OutlinedButton>

View File

@ -1,7 +1,7 @@
import React from 'react';
import Link from 'next/link';
import { useTranslations } from 'next-intl';
import { Link as IntlLink } from '../../../navigation';
import { Link as IntlLink } from '../../../i18n/routing';
import { i18nText } from '../../../i18nKeys';
export const Footer = ({ locale }: { locale: string }) => {

View File

@ -1,9 +1,9 @@
'use client'
import React, { FC, useState, useEffect } from 'react';
import React, { useState, useEffect } from 'react';
import { Button } from 'antd';
import { useSelectedLayoutSegment } from 'next/navigation';
import { Link } from '../../../navigation';
import { Link } from '../../../i18n/routing';
import { AUTH_TOKEN_KEY } from '../../../constants/common';
import { useLocalStorage } from '../../../hooks/useLocalStorage';
import { AuthModal } from '../../Modals/AuthModal';

View File

@ -3,7 +3,7 @@
import React from 'react';
import { useSelectedLayoutSegment } from 'next/navigation';
import dynamic from 'next/dynamic';
import { Link } from '../../../navigation';
import { Link } from '../../../i18n/routing';
type HeaderMenuProps = {
locale: string;

View File

@ -3,7 +3,7 @@
import React, { FC, useState } from 'react';
import dynamic from 'next/dynamic';
import { useSelectedLayoutSegment } from 'next/navigation';
import { Link } from '../../../navigation';
import { Link } from '../../../i18n/routing';
type HeaderMenuMobileProps = {
locale: string;

View File

@ -4,7 +4,7 @@ import React, { useTransition, useState } from 'react';
import { Dropdown } from 'antd';
import type { MenuProps } from 'antd';
import { CaretDownOutlined, CaretUpOutlined } from '@ant-design/icons';
import { useRouter, usePathname } from '../../../navigation';
import { useRouter, usePathname } from '../../../i18n/routing';
import { LOCALES } from '../../../constants/locale';
import { Locale } from '../../../types/locale';

View File

@ -3,7 +3,7 @@ import { HeaderMenu } from './HeaderMenu';
import { LanguageSwitcher } from './LanguageSwitcher';
import { HeaderMobileMenu } from './HeaderMobileMenu';
import { HEAD_ROUTES } from '../../../constants/routes';
import { Link } from '../../../navigation';
import { Link } from '../../../i18n/routing';
import { i18nText } from '../../../i18nKeys';
type HeaderProps = {

View File

@ -81,7 +81,7 @@ export const CheckoutForm: FC<PaymentFormProps> = ({ amount, sessionId, locale }
elements,
clientSecret,
confirmParams: {
return_url: window.location.href,
return_url: window?.location?.href || '',
payment_method_data: {
allow_redisplay: 'limited',
// billing_details: {

View File

@ -2,7 +2,6 @@ import { Locale } from '../types/locale';
export const DEFAULT_LOCALE = Locale.en;
export const ALLOWED_LOCALES = [Locale.en, Locale.ru, Locale.de, Locale.it, Locale.es, Locale.fr] as const;
export const LOCALE_PREFIX = undefined;
export const LOCALES = {
[Locale.en]: 'En',

View File

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

View File

@ -14,21 +14,21 @@ export const useOauthWindow = () => {
if (data.messageType === 'oAuth') {
messageHandler(event);
window.removeEventListener('message', handler);
window?.removeEventListener('message', handler);
}
}
window.removeEventListener('message', handler);
window?.removeEventListener('message', handler);
if (!oauthWindow || oauthWindow.closed) {
// окно ещё не существует, либо было закрыто
oauthWindow = window.open(url, name, params)!;
oauthWindow = window?.open(url, name, params)!;
} else {
// окно уже существует
oauthWindow!.focus();
}
window.addEventListener('message', handler);
window?.addEventListener('message', handler);
}
return {

View File

@ -1,10 +0,0 @@
import { getRequestConfig } from 'next-intl/server';
import { Locale } from './types/locale';
export default getRequestConfig(async ({ locale }) => ({
messages: (
await (locale === Locale.en
? import('../messages/en.json')
: import(`../messages/${locale}.json`))
).default
}));

15
src/i18n/request.ts Normal file
View File

@ -0,0 +1,15 @@
import { getRequestConfig } from 'next-intl/server';
import { routing } from './routing';
export default getRequestConfig(async ({ requestLocale }) => {
let locale = await requestLocale;
if (!locale || !routing.locales.includes(locale as any)) {
locale = routing.defaultLocale;
}
return {
locale,
messages: (await import(`../../messages/${locale}.json`)).default
};
});

10
src/i18n/routing.ts Normal file
View File

@ -0,0 +1,10 @@
import { defineRouting } from 'next-intl/routing';
import { createNavigation } from 'next-intl/navigation';
import { ALLOWED_LOCALES, DEFAULT_LOCALE } from '../constants/locale';
export const routing = defineRouting({
locales: ALLOWED_LOCALES,
defaultLocale: DEFAULT_LOCALE
});
export const { Link, redirect, usePathname, useRouter, getPathname } = createNavigation(routing);

View File

@ -20,6 +20,27 @@ export const onSuccessRequestCallback = (config: InternalAxiosRequestConfig) =>
return newConfig;
};
export const onSuccessRequestJwtCallback = (config: InternalAxiosRequestConfig) => {
const newConfig = { ...config };
if (typeof window !== 'undefined') {
var jwt = localStorage.getItem('bbuddy_token_test');
if(jwt) {
newConfig.headers.set('Authorization', `Bearer ${jwt}`);
}
}
return newConfig;
};
export const onSuccessResponseJwtCallback = (response: AxiosResponse) => {
var header = response.headers['x-new-token'];
if(header) {
localStorage.setItem('bbuddy_token_test', header);
}
return response;
};
export const onSuccessResponseCallback = (response: AxiosResponse) => response;
export const onErrorResponseCallback = (error: any) => Promise.reject(error);
@ -34,3 +55,6 @@ apiClient.interceptors.response.use(
onSuccessResponseCallback,
onErrorResponseCallback
);
apiClient.interceptors.response.use(onSuccessResponseJwtCallback);
apiClient.interceptors.request.use(onSuccessRequestJwtCallback);

View File

@ -1,6 +1,6 @@
import { HubConnection, HubConnectionBuilder, LogLevel } from '@microsoft/signalr';
import { IHttpConnectionOptions } from "@microsoft/signalr/src/IHttpConnectionOptions";
import { AUTH_TOKEN_KEY, BASE_URL } from '../constants/common';
import { BASE_URL } from '../constants/common';
import { IChatMessage } from '../types/chat';
const chatMessageMethodName = 'ReceiveMessage';
@ -24,11 +24,12 @@ const chatsReceiveMessageMethodName = 'ChatsMessageCreated';
class SignalConnector {
private connection: HubConnection;
private events ={} as any;
static instance: SignalConnector;
constructor() {
private events = {} as any;
static instance?: SignalConnector;
constructor({ jwt }: { jwt?: string}) {
console.log('here')
const options = {
accessTokenFactory: () => localStorage.getItem(AUTH_TOKEN_KEY)
accessTokenFactory: () => jwt
} as IHttpConnectionOptions;
this.connection = new HubConnectionBuilder()
.withUrl(`${BASE_URL}/hubs/chat`, options)
@ -69,10 +70,17 @@ class SignalConnector {
this.connection.invoke(sendChatMessagesSeenMethodName, messagesId).then(x => console.log(sendChatMessagesSeenMethodName, x))
}
public static getInstance(): SignalConnector {
if (!SignalConnector.instance)
SignalConnector.instance = new SignalConnector();
return SignalConnector.instance;
public static getInstance({ jwt }: { jwt?: string }): SignalConnector | undefined {
if (!SignalConnector.instance) {
if (jwt) {
SignalConnector.instance = new SignalConnector({ jwt });
return SignalConnector.instance;
} else {
return undefined;
}
} else {
return SignalConnector.instance;
}
}
}

View File

@ -2,7 +2,7 @@ import "server-only";
import Stripe from "stripe";
export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, {
export const stripe = new Stripe(process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY as string, {
apiVersion: "2024-06-20",
appInfo: {
name: "bbuddy-ui",

View File

@ -1,11 +1,7 @@
import createMiddleware from 'next-intl/middleware';
import { ALLOWED_LOCALES, DEFAULT_LOCALE, LOCALE_PREFIX } from './constants/locale';
import { routing } from './i18n/routing';
export default createMiddleware({
locales: ALLOWED_LOCALES,
localePrefix: LOCALE_PREFIX,
defaultLocale: DEFAULT_LOCALE
});
export default createMiddleware(routing);
export const config = {
matcher: ['/', '/(en|ru|de|it|es|fr)/:path*']

View File

@ -1,17 +0,0 @@
import { createLocalizedPathnamesNavigation, Pathnames } from 'next-intl/navigation';
import { ALLOWED_LOCALES, LOCALE_PREFIX } from './constants/locale';
export const { Link, redirect, usePathname, useRouter } =
createLocalizedPathnamesNavigation({
locales: ALLOWED_LOCALES,
pathnames: {
'/': '/',
// '/experts': '/experts',
// '/experts/[expertId]': '/experts/[expertId]',
// '/news': '/news',
// '/privacy-policy': '/privacy-policy',
// '/[userId]': '/[userId]',
// '/[userId]/[...slug]': '/[userId]/[...slug]',
} satisfies Pathnames<typeof ALLOWED_LOCALES>,
localePrefix: LOCALE_PREFIX
});

View File

@ -1,5 +1,5 @@
.b-slider {
margin-top: -105px;
//margin-top: -105px;
h2 {
margin: 40px 0 20px;

View File

@ -30,6 +30,7 @@ export type ProfileRequest = {
faceImage?: any;
isFaceImageKeepExisting?: boolean;
phone?: string;
role?: string;
};
export type PayInfo = {