From bdbe4f4b04eb82c770682770e84802fc64d8bc61 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Sun, 14 Jul 2024 11:36:23 +0300 Subject: [PATCH 01/23] Revert "stripe payment" This reverts commit 3a6c7bd88c358f050c0edbbf2ec290714644f4c9. --- src/actions/experts.ts | 34 +-- src/actions/stripe.ts | 78 ----- src/app/[locale]/(main)/layout.tsx | 5 +- src/app/[locale]/experts/[expertId]/page.tsx | 3 +- src/app/[locale]/layout.tsx | 2 +- src/app/[locale]/payment/@payment/page.tsx | 20 -- src/app/[locale]/payment/page.tsx | 28 -- src/app/[locale]/payment/result/layout.tsx | 19 -- src/app/[locale]/payment/result/page.tsx | 27 -- src/app/api/webhooks/route.ts | 66 ----- src/components/Experts/ExpertDetails.tsx | 33 +-- src/components/Modals/SchedulerModal.tsx | 290 ------------------- src/components/stripe/ElementsForm.tsx | 195 ------------- src/components/stripe/PrintObject.tsx | 10 - src/components/stripe/StripeTestCards.tsx | 19 -- src/lib/stripe.ts | 11 - src/types/experts.ts | 26 -- src/types/payment.ts | 3 - src/utils/get-stripe.ts | 15 - src/utils/stripe-helpers.ts | 30 -- 20 files changed, 9 insertions(+), 905 deletions(-) delete mode 100644 src/actions/stripe.ts delete mode 100644 src/app/[locale]/payment/@payment/page.tsx delete mode 100644 src/app/[locale]/payment/page.tsx delete mode 100644 src/app/[locale]/payment/result/layout.tsx delete mode 100644 src/app/[locale]/payment/result/page.tsx delete mode 100644 src/app/api/webhooks/route.ts delete mode 100644 src/components/Modals/SchedulerModal.tsx delete mode 100644 src/components/stripe/ElementsForm.tsx delete mode 100644 src/components/stripe/PrintObject.tsx delete mode 100644 src/components/stripe/StripeTestCards.tsx delete mode 100644 src/lib/stripe.ts delete mode 100644 src/types/payment.ts delete mode 100644 src/utils/get-stripe.ts delete mode 100644 src/utils/stripe-helpers.ts diff --git a/src/actions/experts.ts b/src/actions/experts.ts index 5ddb524..91e0d73 100644 --- a/src/actions/experts.ts +++ b/src/actions/experts.ts @@ -1,7 +1,5 @@ import { apiClient } from '../lib/apiClient'; -import {GeneralFilter, ExpertsData, ExpertDetails, ExpertScheduler, ExpertSchedulerSession} from '../types/experts'; -import {useLocalStorage} from "../hooks/useLocalStorage"; -import {AUTH_TOKEN_KEY} from "../constants/common"; +import { GeneralFilter, ExpertsData, ExpertDetails } from '../types/experts'; export const getExpertsList = async (locale: string, filter?: GeneralFilter) => { const response = await apiClient.post( @@ -30,33 +28,3 @@ export const getExpertById = async (id: string, locale: string) => { return response.data as ExpertDetails || null; }; - -export const getSchedulerByExpertId = async (expertId: string, locale: string, jwt: string) => { - const response = await apiClient.post( - '/home/sessionsignupdata', - { id: expertId }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ); - - return response.data as ExpertScheduler || null; -}; - -export const getSchedulerSession = async (data: { coachId: number, tagId: number, startAtUtc: string, clientComment: string }, locale: string, jwt: string) => { - const response = await apiClient.post( - '/home/sessionsignupsubmit', - data, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ); - - return response.data as ExpertSchedulerSession || null; -}; \ No newline at end of file diff --git a/src/actions/stripe.ts b/src/actions/stripe.ts deleted file mode 100644 index c0d2254..0000000 --- a/src/actions/stripe.ts +++ /dev/null @@ -1,78 +0,0 @@ -"use server"; - -import {PaymentIntentCreateParams, Stripe} from "stripe"; - -import { headers } from "next/headers"; - -import { formatAmountForStripe } from "../utils/stripe-helpers"; -import { stripe } from "../lib/stripe"; - -export async function createCheckoutSession( - data: FormData, -): Promise<{ client_secret: string | null; url: string | null }> { - 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 = - await stripe.checkout.sessions.create({ - mode: "payment", - submit_type: "donate", - line_items: [ - { - quantity: 1, - price_data: { - currency: 'eur', - product_data: { - name: "Custom amount donation", - }, - unit_amount: formatAmountForStripe( - Number(data.get("customDonation") as string), - 'eur', - ), - }, - }, - ], - ...(ui_mode === "hosted" && { - success_url: `${origin}/payment/with-checkout/result?session_id={CHECKOUT_SESSION_ID}`, - cancel_url: `${origin}/with-checkout`, - }), - ...(ui_mode === "embedded" && { - return_url: `${origin}/payment/with-embedded-checkout/result?session_id={CHECKOUT_SESSION_ID}`, - }), - ui_mode, - }); - - return { - client_secret: checkoutSession.client_secret, - url: checkoutSession.url, - }; -} - -export async function createPaymentIntent( - data: any, -): Promise<{ client_secret: string }> { - - const params = { - amount: formatAmountForStripe( - Number(data['amount'] as string), - 'eur', - ), - automatic_payment_methods: { enabled: true }, - currency: 'eur', - } as PaymentIntentCreateParams; - - // additional params - if (data.sessionId){ - params.metadata = { - sessionId : data.sessionId - } - } - - const paymentIntent: Stripe.PaymentIntent = - await stripe.paymentIntents.create(params); - - return { client_secret: paymentIntent.client_secret as string }; -} \ No newline at end of file diff --git a/src/app/[locale]/(main)/layout.tsx b/src/app/[locale]/(main)/layout.tsx index 7558955..daa1e7d 100644 --- a/src/app/[locale]/(main)/layout.tsx +++ b/src/app/[locale]/(main)/layout.tsx @@ -16,8 +16,7 @@ export default function MainLayout({ children, news, directions, experts }: { children: ReactNode, news: ReactNode, directions: ReactNode, - experts: ReactNode, - payment: ReactNode + experts: ReactNode }) { return ( <> @@ -27,4 +26,4 @@ export default function MainLayout({ children, news, directions, experts }: { {experts} ); -} +}; diff --git a/src/app/[locale]/experts/[expertId]/page.tsx b/src/app/[locale]/experts/[expertId]/page.tsx index 23dd24e..6d1a701 100644 --- a/src/app/[locale]/experts/[expertId]/page.tsx +++ b/src/app/[locale]/experts/[expertId]/page.tsx @@ -11,7 +11,6 @@ import { } from '../../../../components/Experts/ExpertDetails'; import { Details } from '../../../../types/experts'; import { BackButton } from '../../../../components/view/BackButton'; -import {SchedulerModal} from "../../../../components/Modals/SchedulerModal"; export const metadata: Metadata = { title: 'Bbuddy - Experts item', @@ -82,7 +81,7 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: { - +

Expert Background

diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 8eee097..520e6ae 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -39,4 +39,4 @@ export default function LocaleLayout({ children, params: { locale } }: LayoutPro ); -} +}; diff --git a/src/app/[locale]/payment/@payment/page.tsx b/src/app/[locale]/payment/@payment/page.tsx deleted file mode 100644 index c93d91d..0000000 --- a/src/app/[locale]/payment/@payment/page.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { Metadata } from "next"; - -import {ElementsForm} from "../../../../components/stripe/ElementsForm"; - -export const metadata: Metadata = { - title: "Payment", -}; - -export default function PaymentElementPage({ - searchParams, - }: { - searchParams?: { payment_intent_client_secret?: string }; -}) { - return ( -
-

Pay

- -
- ); -} \ No newline at end of file diff --git a/src/app/[locale]/payment/page.tsx b/src/app/[locale]/payment/page.tsx deleted file mode 100644 index 55064d7..0000000 --- a/src/app/[locale]/payment/page.tsx +++ /dev/null @@ -1,28 +0,0 @@ -import React from 'react'; -import type { Metadata } from 'next'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import { useTranslations } from 'next-intl'; -import { GeneralTopSection } from '../../../components/Page'; -import PaymentElementPage from "./@payment/page"; - -export const metadata: Metadata = { - title: 'Bbuddy - Take the lead with BB', - description: 'Bbuddy desc Take the lead with BB' -}; - -export default function BbClientPage({ params: { locale } }: { params: { locale: string } }) { - unstable_setRequestLocale(locale); - const t = useTranslations('BbClient'); - - return ( - <> - -
- -
- - ); -}; diff --git a/src/app/[locale]/payment/result/layout.tsx b/src/app/[locale]/payment/result/layout.tsx deleted file mode 100644 index bea1996..0000000 --- a/src/app/[locale]/payment/result/layout.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { Metadata } from "next"; -import React from "react"; - -export const metadata: Metadata = { - title: "Payment Intent Result", -}; - -export default function ResultLayout({ - children, - }: { - children: React.ReactNode; -}) { - return ( -
-

Payment Intent Result

- {children} -
- ); -} \ No newline at end of file diff --git a/src/app/[locale]/payment/result/page.tsx b/src/app/[locale]/payment/result/page.tsx deleted file mode 100644 index dad26cd..0000000 --- a/src/app/[locale]/payment/result/page.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import type { Stripe } from "stripe"; - -import { stripe} from "../../../../lib/stripe"; -import PrintObject from "../../../../components/stripe/PrintObject"; - -export default async function ResultPage({ - searchParams, - }: { - searchParams: { payment_intent: string }; -}) { - if (!searchParams.payment_intent) - throw new Error("Please provide a valid payment_intent (`pi_...`)"); - - const paymentIntent: Stripe.PaymentIntent = - await stripe.paymentIntents.retrieve(searchParams.payment_intent); - - // Тут под идее тыкнуться в бек на тему того - прошла ли оплата. в зависимости от этого показать что все ок или нет - // также стоит расшить ссылкой КУДА переходить после того как показали что все ок. - - return ( - <> -

Status: {paymentIntent.status}

-

Payment Intent response:

- - - ); -} \ No newline at end of file diff --git a/src/app/api/webhooks/route.ts b/src/app/api/webhooks/route.ts deleted file mode 100644 index 6fa9a39..0000000 --- a/src/app/api/webhooks/route.ts +++ /dev/null @@ -1,66 +0,0 @@ -import type { Stripe } from "stripe"; - -import { NextResponse } from "next/server"; - -import { stripe } from "../../../lib/stripe"; - -export async function POST(req: Request) { - let event: Stripe.Event; - - try { - event = stripe.webhooks.constructEvent( - await (await req.blob()).text(), - req.headers.get("stripe-signature") as string, - process.env.STRIPE_WEBHOOK_SECRET as string, - ); - } catch (err) { - const errorMessage = err instanceof Error ? err.message : "Unknown error"; - // On error, log and return the error message. - if (err! instanceof Error) console.log(err); - console.log(`❌ Error message: ${errorMessage}`); - return NextResponse.json( - { message: `Webhook Error: ${errorMessage}` }, - { status: 400 }, - ); - } - - // Successfully constructed event. - console.log("✅ Success:", event.id); - - const permittedEvents: string[] = [ - "checkout.session.completed", - "payment_intent.succeeded", - "payment_intent.payment_failed", - ]; - - if (permittedEvents.includes(event.type)) { - let data; - - try { - switch (event.type) { - case "checkout.session.completed": - data = event.data.object as Stripe.Checkout.Session; - console.log(`💰 CheckoutSession status: ${data.payment_status}`); - break; - case "payment_intent.payment_failed": - data = event.data.object as Stripe.PaymentIntent; - console.log(`❌ Payment failed: ${data.last_payment_error?.message}`); - break; - case "payment_intent.succeeded": - data = event.data.object as Stripe.PaymentIntent; - console.log(`💰 PaymentIntent status: ${data.status}`); - break; - default: - throw new Error(`Unhandled event: ${event.type}`); - } - } catch (error) { - console.log(error); - return NextResponse.json( - { message: "Webhook handler failed" }, - { status: 500 }, - ); - } - } - // Return a response to acknowledge receipt of the event. - return NextResponse.json({ message: "Received" }, { status: 200 }); -} \ No newline at end of file diff --git a/src/components/Experts/ExpertDetails.tsx b/src/components/Experts/ExpertDetails.tsx index 773a73a..0c06c74 100644 --- a/src/components/Experts/ExpertDetails.tsx +++ b/src/components/Experts/ExpertDetails.tsx @@ -1,36 +1,20 @@ 'use client'; -import React, {FC, useEffect, useState} from 'react'; +import React, { FC } from 'react'; import Image from 'next/image'; import { Tag, Image as AntdImage, Space } from 'antd'; import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons'; -import {ExpertDetails, ExpertDocument, ExpertScheduler} from '../../types/experts'; +import { ExpertDetails, ExpertDocument } from '../../types/experts'; import { Locale } from '../../types/locale'; import { CustomRate } from '../view/CustomRate'; -import {getSchedulerByExpertId} from "../../actions/experts"; -import {useLocalStorage} from "../../hooks/useLocalStorage"; -import {AUTH_TOKEN_KEY} from "../../constants/common"; -import dayjs from "dayjs"; -import {SchedulerModal} from "../Modals/SchedulerModal"; type ExpertDetailsProps = { expert: ExpertDetails; locale?: string; - expertId?: string; }; -export const ExpertCard: FC = ({ expert, locale, expertId }) => { +export const ExpertCard: FC = ({ expert }) => { const { publicCoachDetails } = expert || {}; - const [showSchedulerModal, setShowSchedulerModal] = useState(false); - const [mode, setMode] = useState<'data' | 'time' | 'pay' | 'finish'>('data'); - const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0 } } = expert || {}; - - const onSchedulerHandle = async () => { - console.log('sessionCost', sessionCost); - setMode('data'); - setShowSchedulerModal(true) - // отмаппим. - } return (
@@ -52,7 +36,7 @@ export const ExpertCard: FC = ({ expert, locale, expertId })
- + Schedule @@ -61,15 +45,6 @@ export const ExpertCard: FC = ({ expert, locale, expertId }) Video
- setShowSchedulerModal(false)} - updateMode={setMode} - mode={mode} - expertId={expertId as string} - locale={locale as string} - sessionCost={sessionCost} - /> ); }; diff --git a/src/components/Modals/SchedulerModal.tsx b/src/components/Modals/SchedulerModal.tsx deleted file mode 100644 index 77d6c82..0000000 --- a/src/components/Modals/SchedulerModal.tsx +++ /dev/null @@ -1,290 +0,0 @@ -'use client'; - -import React, {Dispatch, FC, SetStateAction, useEffect, useState} from 'react'; -import { usePathname } from 'next/navigation'; -import classNames from 'classnames'; -import Link from 'next/link'; -import {Modal, Form, Calendar, Radio } from 'antd'; -import type { CalendarProps, RadioChangeEvent } from 'antd'; -import { CloseOutlined } from '@ant-design/icons'; -import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent'; -import dayjs, { Dayjs } from 'dayjs'; -import {ExpertDetails, ExpertScheduler, Tags} from "../../types/experts"; -import { createStyles } from 'antd-style'; -import {useLocalStorage} from "../../hooks/useLocalStorage"; -import {AUTH_TOKEN_KEY} from "../../constants/common"; -import {getSchedulerByExpertId, getSchedulerSession} from "../../actions/experts"; -import {ElementsForm} from "../stripe/ElementsForm"; - -type SchedulerModalProps = { - open: boolean; - handleCancel: () => void; - mode: 'data' | 'time' | 'pay' | 'finish'; - updateMode: (mode: 'data' | 'time' | 'pay' | 'finish') => void; - sessionCost: number; - expertId: string; - locale: string; -}; - -const useStyle = createStyles(({ token, css, cx }) => { - const lunar = css` - color: ${token.colorTextTertiary}; - font-size: ${token.fontSizeSM}px; - `; - return { - wrapper: css` - width: 450px; - border: 1px solid ${token.colorBorderSecondary}; - border-radius: ${token.borderRadiusOuter}; - padding: 5px; - `, - dateCell: css` - position: relative; - &:before { - content: ''; - position: absolute; - left: 0; - right: 0; - top: 0; - bottom: 0; - margin: auto; - max-width: 40px; - max-height: 40px; - background: transparent; - transition: background 300ms; - border-radius: ${token.borderRadiusOuter}px; - border: 1px solid transparent; - box-sizing: border-box; - } - &:hover:before { - background: rgba(0, 0, 0, 0.04); - } - `, - today: css` - &:before { - border: 1px solid ${token.colorPrimary}; - } - `, - text: css` - position: relative; - z-index: 1; - `, - lunar, - current: css` - color: ${token.colorTextLightSolid}; - &:before { - background: ${token.colorPrimary}; - } - &:hover:before { - background: ${token.colorPrimary}; - opacity: 0.8; - } - .${cx(lunar)} { - color: ${token.colorTextLightSolid}; - opacity: 0.9; - } - `, - monthCell: css` - width: 120px; - color: ${token.colorTextBase}; - border-radius: ${token.borderRadiusOuter}px; - padding: 5px 0; - &:hover { - background: rgba(0, 0, 0, 0.04); - } - `, - monthCellCurrent: css` - color: ${token.colorTextLightSolid}; - background: ${token.colorPrimary}; - &:hover { - background: ${token.colorPrimary}; - opacity: 0.8; - } - `, - weekend: css` - color: ${token.colorError}; - &.gray { - opacity: 0.4; - } - `, - }; -}); - -export const SchedulerModal: FC = ({ - open, - handleCancel, - mode, - updateMode, - sessionCost, - locale, - expertId, -}) => { - const { styles } = useStyle({ test: true }); - const [selectDate, setSelectDate] = React.useState(dayjs()); - const [dates, setDates] = React.useState(); - const [tags, setTags] = React.useState([]); - const [tag, setTag] = React.useState(-1); - const [slot, setSlot] = React.useState(''); - const [sessionId, setSessionId] = React.useState(-1); - const [rawScheduler, setRawScheduler] = useState(null); - const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); - - useEffect( ()=> { - async function loadScheduler(){ - const rawScheduler = await getSchedulerByExpertId(expertId as string, locale as string, jwt) - setRawScheduler(rawScheduler) - } - if (open) { - loadScheduler() - } - }, [open]) - - useEffect(() => { - const map = {} as any - rawScheduler?.availableSlots.forEach((el)=>{ - const key = dayjs(el.startTime).format('YYYY-MM-DD'); - if (!map[key]){ - map[key] = [] - } - map[key].push(el) - - }) - console.log(rawScheduler, map) - setDates(map) - setTags(rawScheduler?.tags) - }, [rawScheduler]); - - const onPanelChange = (value: Dayjs, mode: CalendarProps['mode']) => { - console.log(value.format('YYYY-MM-DD'), mode); - }; - - const onDateChange: CalendarProps['onSelect'] = (value, selectInfo) => { - if (selectInfo.source === 'date') { - setSelectDate(value); - updateMode('time') - } - }; - - const disabledDate = (currentDate: Dayjs) => { - return !dates || !dates[currentDate.format('YYYY-MM-DD')] - } - - const handleTimeslot = (e: RadioChangeEvent) => { - setSlot(e.target.value.startTime) - console.log('radio checked', e.target.value); - }; - - const handleTag = (e: RadioChangeEvent) => { - setTag(e.target.value) - console.log('tag radio checked', e.target.value); - }; - - const handleSingupSession = async () => { - const data = {coachId: expertId, tagId: tag, startAtUtc: slot, clientComment:''} - console.log(data) - const session = await getSchedulerSession({coachId: expertId, tagId: tag, startAtUtc: slot, clientComment:''}, locale, jwt) - console.log(session); - // тут должна быть проверка все ли с регистрацией сессии - setSessionId(session?.sessionId) - updateMode('pay') - } - - const currentDay = dayjs() - - const cellRender: CalendarProps['fullCellRender'] = (date, info) => { - const isWeekend = date.day() === 6 || date.day() === 0; - return React.cloneElement(info.originNode, { - ...info.originNode.props, - className: classNames(styles.dateCell, { - [styles.current]: selectDate.isSame(date, 'date'), - [styles.today]: date.isSame(dayjs(), 'date'), - }), - children: ( -
- - {date.get('date')} - -
- ), - }); - } - - - return ( - } - > -
- {tags && ( - - {tags?.map((tag)=>( - {tag.name} - ) - )} - - ) - } -
- {mode === 'data' && ( - { - const start = 0; - const end = 12; - const monthOptions = []; - - let current = currentDay.clone(); - const localeData = value.locale(); - const months = []; - - for(let i=0; i<6; i++){ - const m = current.clone() - months.push(m); - current = current.add(1,'month') - } - return (<> - {months.map((m, i)=>( - - ))} - ) - }} - - /> - )} - {mode === 'time' && ( - <> -
- -
- - - {dates[selectDate.format('YYYY-MM-DD')].map( (el) => { - return ({dayjs(el.startTime).format('hh-mm')} - {dayjs(el.endTime).format('hh-mm')}) - })} - - - - )} - {mode === 'pay' && ( - - )} -
- ); -}; diff --git a/src/components/stripe/ElementsForm.tsx b/src/components/stripe/ElementsForm.tsx deleted file mode 100644 index 0e3da2f..0000000 --- a/src/components/stripe/ElementsForm.tsx +++ /dev/null @@ -1,195 +0,0 @@ -"use client"; - -import type { StripeError } from "@stripe/stripe-js"; - -import * as React from "react"; -import { - useStripe, - useElements, - PaymentElement, - Elements, -} from "@stripe/react-stripe-js"; - -import StripeTestCards from "./StripeTestCards"; - -import getStripe from "../../utils/get-stripe"; -import { createPaymentIntent} from "../../actions/stripe"; -import {Form} from "antd"; -import {Payment} from "../../types/payment"; -import {CustomInput} from "../view/CustomInput"; -import {i18nText} from "../../i18nKeys"; -import {FC, useEffect} from "react"; -import {getPersonalData} from "../../actions/profile"; - -type PaymentFormProps = { - amount: number, - sessionId?: string -} - - -export const CheckoutForm: FC = ({amount, sessionId}) => { - const [input, setInput] = React.useState<{ - paySumm: number; - cardholderName: string; - }>({ - paySumm: 1, - cardholderName: "", - }); - const [form, ] = Form.useForm(); - const formAmount = Form.useWatch('amount', form); - const [paymentType, setPaymentType] = React.useState(""); - const [payment, setPayment] = React.useState<{ - status: "initial" | "processing" | "error"; - }>({ status: "initial" }); - const [errorMessage, setErrorMessage] = React.useState(""); - - const stripe = useStripe(); - const elements = useElements(); - - const PaymentStatus = ({ status }: { status: string }) => { - switch (status) { - case "processing": - case "requires_payment_method": - case "requires_confirmation": - return

Processing...

; - - case "requires_action": - return

Authenticating...

; - - case "succeeded": - return

Payment Succeeded 🥳

; - - case "error": - return ( - <> -

Error 😭

-

{errorMessage}

- - ); - - default: - return null; - } - }; - - useEffect(() => { - elements?.update({ amount: formAmount * 100 }); - }, [formAmount]); - - const handleInputChange: React.ChangeEventHandler = (e) => { - setInput({ - ...input, - [e.currentTarget.name]: e.currentTarget.value, - }); - }; - - const onSubmit = async (data) => { - try { - if (!elements || !stripe) return; - - setPayment({ status: "processing" }); - - const { error: submitError } = await elements.submit(); - - if (submitError) { - setPayment({ status: "error" }); - setErrorMessage(submitError.message ?? "An unknown error occurred"); - - return; - } - - // Create a PaymentIntent with the specified amount. - console.log('DATA', data); - const { client_secret: clientSecret } = await createPaymentIntent( - {amount: amount}, - ); - - // Use your card Element with other Stripe.js APIs - const { error: confirmError } = await stripe!.confirmPayment({ - elements, - clientSecret, - confirmParams: { - return_url: `${window.location.origin}/ru/payment/result`, - payment_method_data: { - allow_redisplay: 'limited', - billing_details: { - name: input.cardholderName, - }, - }, - }, - }); - - if (confirmError) { - setPayment({ status: "error" }); - setErrorMessage(confirmError.message ?? "An unknown error occurred"); - } - } catch (err) { - const { message } = err as StripeError; - - setPayment({ status: "error" }); - setErrorMessage(message ?? "An unknown error occurred"); - } - }; - - - - return ( - <> -
-
- - Your payment details: - {paymentType === "card" ? ( - - ) : null} -
- { - setPaymentType(e.value.type); - }} - /> -
-
- -
- - - ); -} - -export const ElementsForm: FC = ({amount, sessionId}) => { - return ( - - - - ) -} \ No newline at end of file diff --git a/src/components/stripe/PrintObject.tsx b/src/components/stripe/PrintObject.tsx deleted file mode 100644 index b7cd424..0000000 --- a/src/components/stripe/PrintObject.tsx +++ /dev/null @@ -1,10 +0,0 @@ -import type { Stripe } from "stripe"; - -export default function PrintObject({ - content, - }: { - content: Stripe.PaymentIntent | Stripe.Checkout.Session; -}): JSX.Element { - const formattedContent: string = JSON.stringify(content, null, 2); - return
{formattedContent}
; -} \ No newline at end of file diff --git a/src/components/stripe/StripeTestCards.tsx b/src/components/stripe/StripeTestCards.tsx deleted file mode 100644 index cf40281..0000000 --- a/src/components/stripe/StripeTestCards.tsx +++ /dev/null @@ -1,19 +0,0 @@ -export default function StripeTestCards(): JSX.Element { - return ( -
- Use any of the{" "} - - Stripe test cards - {" "} - for demo, e.g.{" "} -
- 4242424242424242 -
- . -
- ); -} \ No newline at end of file diff --git a/src/lib/stripe.ts b/src/lib/stripe.ts deleted file mode 100644 index 08773a7..0000000 --- a/src/lib/stripe.ts +++ /dev/null @@ -1,11 +0,0 @@ -import "server-only"; - -import Stripe from "stripe"; - -export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY as string, { - apiVersion: "2024-06-20", - appInfo: { - name: "bbuddy-ui", - url: "", - }, -}); \ No newline at end of file diff --git a/src/types/experts.ts b/src/types/experts.ts index 3df393d..160db87 100644 --- a/src/types/experts.ts +++ b/src/types/experts.ts @@ -112,29 +112,3 @@ export type ExpertDetails = { associations?: Association[]; associationLevels?: AssociationLevel[]; }; - -export type Tags = { - id: number, - groupId: number, - name: string, - couchCount: number, - group: { - id: number, - name: string, - tags: string[]; - } -} - -export type Slot = { - startTime: string; - endTime: string; -} - -export type ExpertScheduler = { - tags: Tags[], - availableSlots: Slot[]; -} - -export type ExpertSchedulerSession = { - sessionId: string -} \ No newline at end of file diff --git a/src/types/payment.ts b/src/types/payment.ts deleted file mode 100644 index cdadd42..0000000 --- a/src/types/payment.ts +++ /dev/null @@ -1,3 +0,0 @@ -export type Payment = { - amount: number; -} \ No newline at end of file diff --git a/src/utils/get-stripe.ts b/src/utils/get-stripe.ts deleted file mode 100644 index 6930d5b..0000000 --- a/src/utils/get-stripe.ts +++ /dev/null @@ -1,15 +0,0 @@ -/** - * This is a singleton to ensure we only instantiate Stripe once. - */ -import { Stripe, loadStripe } from "@stripe/stripe-js"; - -let stripePromise: Promise; - -export default function getStripe(): Promise { - if (!stripePromise) - stripePromise = loadStripe( - process.env.NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY as string, - ); - - return stripePromise; -} \ No newline at end of file diff --git a/src/utils/stripe-helpers.ts b/src/utils/stripe-helpers.ts deleted file mode 100644 index 09144bd..0000000 --- a/src/utils/stripe-helpers.ts +++ /dev/null @@ -1,30 +0,0 @@ -export function formatAmountForDisplay( - amount: number, - currency: string, -): string { - let numberFormat = new Intl.NumberFormat(["en-US"], { - style: "currency", - currency: currency, - currencyDisplay: "symbol", - }); - return numberFormat.format(amount); -} - -export function formatAmountForStripe( - amount: number, - currency: string, -): number { - let numberFormat = new Intl.NumberFormat(["en-US"], { - style: "currency", - currency: currency, - currencyDisplay: "symbol", - }); - const parts = numberFormat.formatToParts(amount); - let zeroDecimalCurrency: boolean = true; - for (let part of parts) { - if (part.type === "decimal") { - zeroDecimalCurrency = false; - } - } - return zeroDecimalCurrency ? amount : Math.round(amount * 100); -} \ No newline at end of file From e926d4cb4a33a27cefea83140920c0451b2cc0e6 Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 17 Jul 2024 21:48:15 +0200 Subject: [PATCH 02/23] add release to pipeline --- Jenkinsfile | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2171d7f..bc1fbfd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -1,14 +1,18 @@ pipeline { agent { label 'jenkins-nodejs-agent' } - - - + environment { + RELEASE = "0.0.1" + } stages { stage('Build static content') { steps { sh ''' - docker build --progress=plain -t bbuddy/bbuddy_ui:latest . + npm install + npm run build + pwd + echo + docker build --progress=plain -t bbuddy/bbuddy_ui:${RELEASE} . ''' } } @@ -16,10 +20,10 @@ pipeline { steps { sh ''' sudo docker login https://harbor-wtkp3fsbv6.vertexa.devbay.tech/ -u 'robot$jenkins' -p 'ZrzsVIAeueW1p0alpAnPfM5CDtaRVVKz' - sudo docker tag bbuddy/bbuddy_ui:latest harbor-wtkp3fsbv6.vertexa.devbay.tech/bbuddy/bbuddy_ui:latest - sudo docker push harbor-wtkp3fsbv6.vertexa.devbay.tech/bbuddy/bbuddy_ui:latest + sudo docker tag bbuddy/bbuddy_ui:${RELEASE} harbor-wtkp3fsbv6.vertexa.devbay.tech/bbuddy/bbuddy_ui:${RELEASE} + sudo docker push harbor-wtkp3fsbv6.vertexa.devbay.tech/bbuddy/bbuddy_ui:${RELEASE} ''' } } } -} \ No newline at end of file +} From a9387b1f281bffcfe2fdd8ef14c190b6030def83 Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 17 Jul 2024 21:58:08 +0200 Subject: [PATCH 03/23] RELEASE set to latest --- Jenkinsfile | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index bc1fbfd..9731ebd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,16 +2,16 @@ pipeline { agent { label 'jenkins-nodejs-agent' } environment { - RELEASE = "0.0.1" + RELEASE = "latest" } stages { stage('Build static content') { steps { sh ''' - npm install - npm run build - pwd - echo + #npm install + #npm run build + #pwd + #echo docker build --progress=plain -t bbuddy/bbuddy_ui:${RELEASE} . ''' } From 020ba600d9d653b37e7a862492a1d23d6a5c49f9 Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 17 Jul 2024 22:51:54 +0200 Subject: [PATCH 04/23] v 0.0.1 --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9731ebd..f8d871d 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { label 'jenkins-nodejs-agent' } environment { - RELEASE = "latest" + RELEASE = "0.0.1" } stages { stage('Build static content') { From b36efa0ddfb1224a515f06ff5c4cd240831c632c Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 17 Jul 2024 23:14:16 +0200 Subject: [PATCH 05/23] RELEASE = "latest" --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index f8d871d..9731ebd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { label 'jenkins-nodejs-agent' } environment { - RELEASE = "0.0.1" + RELEASE = "latest" } stages { stage('Build static content') { From ec5fb6d443988f0a0ae29c0368f2f916a7f70765 Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 24 Jul 2024 15:53:32 +0200 Subject: [PATCH 06/23] build tag 0.0.2 --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 9731ebd..2551bc5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { label 'jenkins-nodejs-agent' } environment { - RELEASE = "latest" + RELEASE = "0.0.2" } stages { stage('Build static content') { From 4a00d715dfd322ed3323a72dc712071837a76fa8 Mon Sep 17 00:00:00 2001 From: Witalij Poljatchek Date: Wed, 24 Jul 2024 16:01:27 +0200 Subject: [PATCH 07/23] release latest --- Jenkinsfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Jenkinsfile b/Jenkinsfile index 2551bc5..9731ebd 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent { label 'jenkins-nodejs-agent' } environment { - RELEASE = "0.0.2" + RELEASE = "latest" } stages { stage('Build static content') { From abf04b4c5b60c2389813d99df2fdaebd045707d2 Mon Sep 17 00:00:00 2001 From: SD Date: Mon, 29 Jul 2024 23:57:04 +0400 Subject: [PATCH 08/23] feat: add more translate, fix styles --- messages/de.json | 18 +- messages/en.json | 6 +- messages/es.json | 18 +- messages/fr.json | 18 +- messages/it.json | 18 +- messages/ru.json | 16 +- public/favicon.ico | Bin 25931 -> 4286 bytes src/actions/auth.ts | 36 +-- src/actions/experts.ts | 40 ++-- src/actions/helpers.ts | 43 ++++ src/actions/hooks/useProfileSettings.ts | 2 +- src/actions/hooks/useSessionDetails.ts | 4 +- src/actions/profile.ts | 40 ++-- src/actions/sessions.ts | 216 +++++++----------- src/actions/tags.ts | 37 +-- src/app/[locale]/(main)/@news/page.tsx | 16 +- src/app/[locale]/(main)/layout.tsx | 4 +- .../(account)/work-with-us/coaching/page.tsx | 10 +- .../account/(account)/work-with-us/page.tsx | 18 +- src/app/[locale]/experts/[expertId]/page.tsx | 16 +- src/components/Account/AccountMenu.tsx | 18 -- src/components/Account/ProfileSettings.tsx | 75 ++++-- .../sessions/SessionDetailsContent.tsx | 46 ++-- .../Account/sessions/SessionsTabs.tsx | 8 +- src/components/Experts/ExpertDetails.tsx | 43 ++-- src/components/Experts/ExpertsList.tsx | 1 + src/components/Experts/Filter.tsx | 4 +- src/components/Modals/AddCommentModal.tsx | 6 +- src/components/Modals/AuthModal.tsx | 11 +- src/components/Modals/DeclineSessionModal.tsx | 10 +- .../Modals/authModalContent/EnterContent.tsx | 20 +- .../Modals/authModalContent/FinishContent.tsx | 10 +- .../authModalContent/RegisterContent.tsx | 22 +- .../Modals/authModalContent/ResetContent.tsx | 6 +- .../Page/GeneralTopSection/index.tsx | 2 +- .../Page/Header/HeaderAuthLinks.tsx | 1 + src/components/Page/Header/HeaderMenu.tsx | 3 +- src/components/Page/Header/index.tsx | 2 +- src/components/view/FilledButton.tsx | 6 + src/components/view/OutlinedButton.tsx | 2 +- src/constants/routes.ts | 3 +- src/i18nKeys/de.ts | 60 ++++- src/i18nKeys/en.ts | 60 ++++- src/i18nKeys/es.ts | 60 ++++- src/i18nKeys/fr.ts | 60 ++++- src/i18nKeys/it.ts | 60 ++++- src/i18nKeys/ru.ts | 60 ++++- src/styles/_default.scss | 50 ++-- src/styles/_form.scss | 15 ++ src/styles/_main.scss | 30 ++- src/styles/_pages.scss | 66 ++++-- src/styles/view/_buttons.scss | 20 +- src/styles/view/_slider.scss | 3 +- 53 files changed, 873 insertions(+), 546 deletions(-) create mode 100644 src/actions/helpers.ts diff --git a/messages/de.json b/messages/de.json index 5ca66b1..14d749a 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1,20 +1,10 @@ { - "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", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", - "news": "Professional Articles & Project News", + "header": "BBuddy: Plattform für persönlichen und beruflichen Erfolg", + "header-desc": "Erhalten Sie Beratungen von führenden Coaches und Mentoren auf BBuddy. Unsere Experten helfen Ihnen, sich zu entwickeln, zu lernen und Ihre persönlichen und beruflichen Ziele zu erreichen. Nutzen Sie unsere Web-Plattform und mobile App für professionelle Unterstützung und Wachstum.", + "news": "Fachartikel & Projektneuigkeiten", "popular": "Popular Topics" }, "BbClient": { @@ -89,7 +79,7 @@ } }, "Experts": { - "title": "Find a expert", + "title": "Find an expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/en.json b/messages/en.json index cbee258..1765bc8 100644 --- a/messages/en.json +++ b/messages/en.json @@ -2,8 +2,8 @@ "Main": { "title": "Bbuddy - Main", "description": "Bbuddy desc", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", + "header": "BBuddy: Platform for Personal and Career Success", + "header-desc": "Receive consultations from leading coaches and mentors on BBuddy. Our experts will help you develop, learn, and achieve your personal and career goals. Use our web platform and mobile app for professional support and growth.", "news": "Professional Articles & Project News", "popular": "Popular Topics" }, @@ -69,7 +69,7 @@ } }, "Experts": { - "title": "Find a expert", + "title": "Find an expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/es.json b/messages/es.json index 5ca66b1..0342887 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1,20 +1,10 @@ { - "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", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", - "news": "Professional Articles & Project News", + "header": "BBuddy: Plataforma para el éxito personal y profesional", + "header-desc": "Reciba consultas de entrenadores y mentores líderes en BBuddy. Nuestros expertos le ayudarán a desarrollarse, aprender y alcanzar sus objetivos personales y profesionales. Utilice nuestra plataforma web y aplicación móvil para apoyo profesional y crecimiento.", + "news": "Artículos profesionales y Noticias de proyectos", "popular": "Popular Topics" }, "BbClient": { @@ -89,7 +79,7 @@ } }, "Experts": { - "title": "Find a expert", + "title": "Find an expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/fr.json b/messages/fr.json index 5ca66b1..48b42e3 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1,20 +1,10 @@ { - "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", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", - "news": "Professional Articles & Project News", + "header": "BBuddy: Plateforme pour le succès personnel et professionnel", + "header-desc": "Recevez des consultations de coachs et mentors de premier plan sur BBuddy. Nos experts vous aideront à développer, apprendre et atteindre vos objectifs personnels et professionnels. Utilisez notre plateforme web et notre application mobile pour un soutien professionnel et une croissance.", + "news": "Articles professionnels et actualités des projets", "popular": "Popular Topics" }, "BbClient": { @@ -89,7 +79,7 @@ } }, "Experts": { - "title": "Find a expert", + "title": "Find an expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/it.json b/messages/it.json index 5ca66b1..379adca 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1,20 +1,10 @@ { - "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", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", - "news": "Professional Articles & Project News", + "header": "BBuddy: Piattaforma per il successo personale e professionale", + "header-desc": "Ricevi consulenze da coach e mentori leader su BBuddy. I nostri esperti ti aiuteranno a svilupparti, imparare e raggiungere i tuoi obiettivi personali e professionali. Usa la nostra piattaforma web e l'app mobile per supporto professionale e crescita.", + "news": "Articoli professionali e novità sui progetti", "popular": "Popular Topics" }, "BbClient": { @@ -89,7 +79,7 @@ } }, "Experts": { - "title": "Find a expert", + "title": "Find an expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/ru.json b/messages/ru.json index 64fb88a..94d8e2e 100644 --- a/messages/ru.json +++ b/messages/ru.json @@ -1,20 +1,10 @@ { - "Header": { - "registration": "Регистрация", - "enter": "Вход", - "account": "Мой аккаунт", - "menu": { - "bb-client": "Начни вместе с BB", - "bb-expert": "Стань BB экспертом", - "blog": "Блог&Новости" - } - }, "Main": { "title": "Bbuddy - Главная", "description": "Bbuddy описание", - "header": "Mentorship, Career\nDevelopment & Coaching.", - "header-desc": "The ins-and-outs of building a career in tech, gaining experience from a mentor, and getting your feet wet with coaching.", - "news": "Professional Articles & Project News", + "header": "BBuddy: Платформа для Личного и Карьерного Успеха", + "header-desc": "Получайте консультации от ведущих коучей и менторов в BBuddy. Наши эксперты помогут вам развиваться, обучаться и достигать личных и карьерных целей. Используйте нашу веб-платформу и мобильное приложение для получения профессиональной поддержки и роста.", + "news": "Профессиональные статьи и новости проекта", "popular": "Popular Topics" }, "BbClient": { diff --git a/public/favicon.ico b/public/favicon.ico index 718d6fea4835ec2d246af9800eddb7ffb276240c..f285885408be9880d0b9297bac0b264c49b80d4b 100644 GIT binary patch literal 4286 zcmeH~TWC~A7=Zt{jj`UgSO}B|Q4|%m)oKz*lr}1gLWy7!Nn*39lvV_*K7?K0F_MfxGzEUdI;To~_zAaWuoiZKXfLfRflfd*nRr>cGG5!8r+UCIyxByAG1SjAV z{9`@-D4uHA3Wwnw9E43UAD(jS^E3E|rbu41WK7c%O6ayRS`5TrPdfI#9RT+P7joale4?;Ru|8i*Osf<66hFJBASdcyc=p zt*{lE;C-loY4D1DwRnhqwMgRUmWb6_{a-#NuCE-;^8_e^I`{x`A#DCP<8ab)eOA)G zT-IUzP=D0g`OY?CeF-1I3Rnblpb*lIEg=7?cze^hd)&U8Go3`oLI<~ZUcL3dasLL7 zpaTxUckmfBz*5K$@UISWKc;;pSO->F(2Za0^WH)A2Nph0sajk{`Y+TVym_4p8)?2Trc?i zyF&cU8Ti-w{2R&N3Gu(^@#j<5G5P07pZ8?oUzhSvVC`F&e+U2l0Kc9=LqX@i0WLw1 zTiZU(!e5Ku!T&G*J0boYVypl6H11ydUd_T^o$^1ze=o#8B;{{UnpjlYlldk4rr$gl1K%{|Nf8#sR)#X44bH^gt&zYB-id)KLd4eq(G zZYAUVJ4?peH)dAzTUcxzs)>jA^HTBkov-(S3K)}WbG@dXk-xJzD2%fxDq zoh6RR&y@Q68)4!PRA%IN&)s5)AFCAJJHk8v=Mew!fxM%|BjX9UaMp149NchZQk?Oj5V+f$bUKhy>J}bLGPM>!5X`JmLx9D?T@q1 zz3b{-s|8Mh4Z6qwfKd?W^S1~13viXdVu(Q_=zVCR-nZ;7?p!~z{>?FsKNa*&RRya+ z$D?Gu{{Ha{KL2n2FJ`GdpGeZ_%h}M>CsODZTlp|U U9(5%Oh0xUYanuRIV{)rc0C6y+82|tP literal 25931 zcmeHv30#a{`}aL_*G&7qml|y<+KVaDM2m#dVr!KsA!#An?kSQM(q<_dDNCpjEux83 zLb9Z^XxbDl(w>%i@8hT6>)&Gu{h#Oeyszu?xtw#Zb1mO{pgX9699l+Qppw7jXaYf~-84xW z)w4x8?=youko|}Vr~(D$UXIbiXABHh`p1?nn8Po~fxRJv}|0e(BPs|G`(TT%kKVJAdg5*Z|x0leQq0 zkdUBvb#>9F()jo|T~kx@OM8$9wzs~t2l;K=woNssA3l6|sx2r3+kdfVW@e^8e*E}v zA1y5{bRi+3Z`uD3{F7LgFJDdvm;nJilkzDku>BwXH(8ItVCXk*-lSJnR?-2UN%hJ){&rlvg`CDTj z)Bzo!3v7Ou#83zEDEFcKt(f1E0~=rqeEbTnMvWR#{+9pg%7G8y>u1OVRUSoox-ovF z2Ydma(;=YuBY(eI|04{hXzZD6_f(v~H;C~y5=DhAC{MMS>2fm~1H_t2$56pc$NH8( z5bH|<)71dV-_oCHIrzrT`2s-5w_+2CM0$95I6X8p^r!gHp+j_gd;9O<1~CEQQGS8) zS9Qh3#p&JM-G8rHekNmKVewU;pJRcTAog68KYo^dRo}(M>36U4Us zfgYWSiHZL3;lpWT=zNAW>Dh#mB!_@Lg%$ms8N-;aPqMn+C2HqZgz&9~Eu z4|Kp<`$q)Uw1R?y(~S>ePdonHxpV1#eSP1B;Ogo+-Pk}6#0GsZZ5!||ev2MGdh}_m z{DeR7?0-1^zVs&`AV6Vt;r3`I`OI_wgs*w=eO%_#7Kepl{B@xiyCANc(l zzIyd4y|c6PXWq9-|KM8(zIk8LPk(>a)zyFWjhT!$HJ$qX1vo@d25W<fvZQ2zUz5WRc(UnFMKHwe1| zWmlB1qdbiA(C0jmnV<}GfbKtmcu^2*P^O?MBLZKt|As~ge8&AAO~2K@zbXelK|4T<{|y4`raF{=72kC2Kn(L4YyenWgrPiv z@^mr$t{#X5VuIMeL!7Ab6_kG$&#&5p*Z{+?5U|TZ`B!7llpVmp@skYz&n^8QfPJzL z0G6K_OJM9x+Wu2gfN45phANGt{7=C>i34CV{Xqlx(fWpeAoj^N0Biu`w+MVcCUyU* zDZuzO0>4Z6fbu^T_arWW5n!E45vX8N=bxTVeFoep_G#VmNlQzAI_KTIc{6>c+04vr zx@W}zE5JNSU>!THJ{J=cqjz+4{L4A{Ob9$ZJ*S1?Ggg3klFp!+Y1@K+pK1DqI|_gq z5ZDXVpge8-cs!o|;K73#YXZ3AShj50wBvuq3NTOZ`M&qtjj#GOFfgExjg8Gn8>Vq5 z`85n+9|!iLCZF5$HJ$Iu($dm?8~-ofu}tEc+-pyke=3!im#6pk_Wo8IA|fJwD&~~F zc16osQ)EBo58U7XDuMexaPRjU@h8tXe%S{fA0NH3vGJFhuyyO!Uyl2^&EOpX{9As0 zWj+P>{@}jxH)8|r;2HdupP!vie{sJ28b&bo!8`D^x}TE$%zXNb^X1p@0PJ86`dZyj z%ce7*{^oo+6%&~I!8hQy-vQ7E)0t0ybH4l%KltWOo~8cO`T=157JqL(oq_rC%ea&4 z2NcTJe-HgFjNg-gZ$6!Y`SMHrlj}Etf7?r!zQTPPSv}{so2e>Fjs1{gzk~LGeesX%r(Lh6rbhSo_n)@@G-FTQy93;l#E)hgP@d_SGvyCp0~o(Y;Ee8{ zdVUDbHm5`2taPUOY^MAGOw*>=s7=Gst=D+p+2yON!0%Hk` zz5mAhyT4lS*T3LS^WSxUy86q&GnoHxzQ6vm8)VS}_zuqG?+3td68_x;etQAdu@sc6 zQJ&5|4(I?~3d-QOAODHpZ=hlSg(lBZ!JZWCtHHSj`0Wh93-Uk)_S%zsJ~aD>{`A0~ z9{AG(e|q3g5B%wYKRxiL2Y$8(4w6bzchKuloQW#e&S3n+P- z8!ds-%f;TJ1>)v)##>gd{PdS2Oc3VaR`fr=`O8QIO(6(N!A?pr5C#6fc~Ge@N%Vvu zaoAX2&(a6eWy_q&UwOhU)|P3J0Qc%OdhzW=F4D|pt0E4osw;%<%Dn58hAWD^XnZD= z>9~H(3bmLtxpF?a7su6J7M*x1By7YSUbxGi)Ot0P77`}P3{)&5Un{KD?`-e?r21!4vTTnN(4Y6Lin?UkSM z`MXCTC1@4A4~mvz%Rh2&EwY))LeoT=*`tMoqcEXI>TZU9WTP#l?uFv+@Dn~b(>xh2 z;>B?;Tz2SR&KVb>vGiBSB`@U7VIWFSo=LDSb9F{GF^DbmWAfpms8Sx9OX4CnBJca3 zlj9(x!dIjN?OG1X4l*imJNvRCk}F%!?SOfiOq5y^mZW)jFL@a|r-@d#f7 z2gmU8L3IZq0ynIws=}~m^#@&C%J6QFo~Mo4V`>v7MI-_!EBMMtb%_M&kvAaN)@ZVw z+`toz&WG#HkWDjnZE!6nk{e-oFdL^$YnbOCN}JC&{$#$O27@|Tn-skXr)2ml2~O!5 zX+gYoxhoc7qoU?C^3~&!U?kRFtnSEecWuH0B0OvLodgUAi}8p1 zrO6RSXHH}DMc$&|?D004DiOVMHV8kXCP@7NKB zgaZq^^O<7PoKEp72kby@W0Z!Y*Ay{&vfg#C&gG@YVR9g?FEocMUi1gSN$+V+ayF45{a zuDZDTN}mS|;BO%gEf}pjBfN2-gIrU#G5~cucA;dokXW89%>AyXJJI z9X4UlIWA|ZYHgbI z5?oFk@A=Ik7lrEQPDH!H+b`7_Y~aDb_qa=B2^Y&Ow41cU=4WDd40dp5(QS-WMN-=Y z9g;6_-JdNU;|6cPwf$ak*aJIcwL@1n$#l~zi{c{EW?T;DaW*E8DYq?Umtz{nJ&w-M zEMyTDrC&9K$d|kZe2#ws6)L=7K+{ zQw{XnV6UC$6-rW0emqm8wJoeZK)wJIcV?dST}Z;G0Arq{dVDu0&4kd%N!3F1*;*pW zR&qUiFzK=@44#QGw7k1`3t_d8&*kBV->O##t|tonFc2YWrL7_eqg+=+k;!F-`^b8> z#KWCE8%u4k@EprxqiV$VmmtiWxDLgnGu$Vs<8rppV5EajBXL4nyyZM$SWVm!wnCj-B!Wjqj5-5dNXukI2$$|Bu3Lrw}z65Lc=1G z^-#WuQOj$hwNGG?*CM_TO8Bg-1+qc>J7k5c51U8g?ZU5n?HYor;~JIjoWH-G>AoUP ztrWWLbRNqIjW#RT*WqZgPJXU7C)VaW5}MiijYbABmzoru6EmQ*N8cVK7a3|aOB#O& zBl8JY2WKfmj;h#Q!pN%9o@VNLv{OUL?rixHwOZuvX7{IJ{(EdPpuVFoQqIOa7giLVkBOKL@^smUA!tZ1CKRK}#SSM)iQHk)*R~?M!qkCruaS!#oIL1c z?J;U~&FfH#*98^G?i}pA{ z9Jg36t4=%6mhY(quYq*vSxptes9qy|7xSlH?G=S@>u>Ebe;|LVhs~@+06N<4CViBk zUiY$thvX;>Tby6z9Y1edAMQaiH zm^r3v#$Q#2T=X>bsY#D%s!bhs^M9PMAcHbCc0FMHV{u-dwlL;a1eJ63v5U*?Q_8JO zT#50!RD619#j_Uf))0ooADz~*9&lN!bBDRUgE>Vud-i5ck%vT=r^yD*^?Mp@Q^v+V zG#-?gKlr}Eeqifb{|So?HM&g91P8|av8hQoCmQXkd?7wIJwb z_^v8bbg`SAn{I*4bH$u(RZ6*xUhuA~hc=8czK8SHEKTzSxgbwi~9(OqJB&gwb^l4+m`k*Q;_?>Y-APi1{k zAHQ)P)G)f|AyjSgcCFps)Fh6Bca*Xznq36!pV6Az&m{O8$wGFD? zY&O*3*J0;_EqM#jh6^gMQKpXV?#1?>$ml1xvh8nSN>-?H=V;nJIwB07YX$e6vLxH( zqYwQ>qxwR(i4f)DLd)-$P>T-no_c!LsN@)8`e;W@)-Hj0>nJ-}Kla4-ZdPJzI&Mce zv)V_j;(3ERN3_@I$N<^|4Lf`B;8n+bX@bHbcZTopEmDI*Jfl)-pFDvo6svPRoo@(x z);_{lY<;);XzT`dBFpRmGrr}z5u1=pC^S-{ce6iXQlLGcItwJ^mZx{m$&DA_oEZ)B{_bYPq-HA zcH8WGoBG(aBU_j)vEy+_71T34@4dmSg!|M8Vf92Zj6WH7Q7t#OHQqWgFE3ARt+%!T z?oLovLVlnf?2c7pTc)~cc^($_8nyKwsN`RA-23ed3sdj(ys%pjjM+9JrctL;dy8a( z@en&CQmnV(()bu|Y%G1-4a(6x{aLytn$T-;(&{QIJB9vMox11U-1HpD@d(QkaJdEb zG{)+6Dos_L+O3NpWo^=gR?evp|CqEG?L&Ut#D*KLaRFOgOEK(Kq1@!EGcTfo+%A&I z=dLbB+d$u{sh?u)xP{PF8L%;YPPW53+@{>5W=Jt#wQpN;0_HYdw1{ksf_XhO4#2F= zyPx6Lx2<92L-;L5PD`zn6zwIH`Jk($?Qw({erA$^bC;q33hv!d!>%wRhj# zal^hk+WGNg;rJtb-EB(?czvOM=H7dl=vblBwAv>}%1@{}mnpUznfq1cE^sgsL0*4I zJ##!*B?=vI_OEVis5o+_IwMIRrpQyT_Sq~ZU%oY7c5JMIADzpD!Upz9h@iWg_>>~j zOLS;wp^i$-E?4<_cp?RiS%Rd?i;f*mOz=~(&3lo<=@(nR!_Rqiprh@weZlL!t#NCc zO!QTcInq|%#>OVgobj{~ixEUec`E25zJ~*DofsQdzIa@5^nOXj2T;8O`l--(QyU^$t?TGY^7#&FQ+2SS3B#qK*k3`ye?8jUYSajE5iBbJls75CCc(m3dk{t?- zopcER9{Z?TC)mk~gpi^kbbu>b-+a{m#8-y2^p$ka4n60w;Sc2}HMf<8JUvhCL0B&Btk)T`ctE$*qNW8L$`7!r^9T+>=<=2qaq-;ll2{`{Rg zc5a0ZUI$oG&j-qVOuKa=*v4aY#IsoM+1|c4Z)<}lEDvy;5huB@1RJPquU2U*U-;gu z=En2m+qjBzR#DEJDO`WU)hdd{Vj%^0V*KoyZ|5lzV87&g_j~NCjwv0uQVqXOb*QrQ zy|Qn`hxx(58c70$E;L(X0uZZ72M1!6oeg)(cdKO ze0gDaTz+ohR-#d)NbAH4x{I(21yjwvBQfmpLu$)|m{XolbgF!pmsqJ#D}(ylp6uC> z{bqtcI#hT#HW=wl7>p!38sKsJ`r8}lt-q%Keqy%u(xk=yiIJiUw6|5IvkS+#?JTBl z8H5(Q?l#wzazujH!8o>1xtn8#_w+397*_cy8!pQGP%K(Ga3pAjsaTbbXJlQF_+m+-UpUUent@xM zg%jqLUExj~o^vQ3Gl*>wh=_gOr2*|U64_iXb+-111aH}$TjeajM+I20xw(((>fej-@CIz4S1pi$(#}P7`4({6QS2CaQS4NPENDp>sAqD z$bH4KGzXGffkJ7R>V>)>tC)uax{UsN*dbeNC*v}#8Y#OWYwL4t$ePR?VTyIs!wea+ z5Urmc)X|^`MG~*dS6pGSbU+gPJoq*^a=_>$n4|P^w$sMBBy@f*Z^Jg6?n5?oId6f{ z$LW4M|4m502z0t7g<#Bx%X;9<=)smFolV&(V^(7Cv2-sxbxopQ!)*#ZRhTBpx1)Fc zNm1T%bONzv6@#|dz(w02AH8OXe>kQ#1FMCzO}2J_mST)+ExmBr9cva-@?;wnmWMOk z{3_~EX_xadgJGv&H@zK_8{(x84`}+c?oSBX*Ge3VdfTt&F}yCpFP?CpW+BE^cWY0^ zb&uBN!Ja3UzYHK-CTyA5=L zEMW{l3Usky#ly=7px648W31UNV@K)&Ub&zP1c7%)`{);I4b0Q<)B}3;NMG2JH=X$U zfIW4)4n9ZM`-yRj67I)YSLDK)qfUJ_ij}a#aZN~9EXrh8eZY2&=uY%2N0UFF7<~%M zsB8=erOWZ>Ct_#^tHZ|*q`H;A)5;ycw*IcmVxi8_0Xk}aJA^ath+E;xg!x+As(M#0=)3!NJR6H&9+zd#iP(m0PIW8$ z1Y^VX`>jm`W!=WpF*{ioM?C9`yOR>@0q=u7o>BP-eSHqCgMDj!2anwH?s%i2p+Q7D zzszIf5XJpE)IG4;d_(La-xenmF(tgAxK`Y4sQ}BSJEPs6N_U2vI{8=0C_F?@7<(G; zo$~G=8p+076G;`}>{MQ>t>7cm=zGtfbdDXm6||jUU|?X?CaE?(<6bKDYKeHlz}DA8 zXT={X=yp_R;HfJ9h%?eWvQ!dRgz&Su*JfNt!Wu>|XfU&68iRikRrHRW|ZxzRR^`eIGt zIeiDgVS>IeExKVRWW8-=A=yA`}`)ZkWBrZD`hpWIxBGkh&f#ijr449~m`j6{4jiJ*C!oVA8ZC?$1RM#K(_b zL9TW)kN*Y4%^-qPpMP7d4)o?Nk#>aoYHT(*g)qmRUb?**F@pnNiy6Fv9rEiUqD(^O zzyS?nBrX63BTRYduaG(0VVG2yJRe%o&rVrLjbxTaAFTd8s;<<@Qs>u(<193R8>}2_ zuwp{7;H2a*X7_jryzriZXMg?bTuegABb^87@SsKkr2)0Gyiax8KQWstw^v#ix45EVrcEhr>!NMhprl$InQMzjSFH54x5k9qHc`@9uKQzvL4ihcq{^B zPrVR=o_ic%Y>6&rMN)hTZsI7I<3&`#(nl+3y3ys9A~&^=4?PL&nd8)`OfG#n zwAMN$1&>K++c{^|7<4P=2y(B{jJsQ0a#U;HTo4ZmWZYvI{+s;Td{Yzem%0*k#)vjpB zia;J&>}ICate44SFYY3vEelqStQWFihx%^vQ@Do(sOy7yR2@WNv7Y9I^yL=nZr3mb zXKV5t@=?-Sk|b{XMhA7ZGB@2hqsx}4xwCW!in#C zI@}scZlr3-NFJ@NFaJlhyfcw{k^vvtGl`N9xSo**rDW4S}i zM9{fMPWo%4wYDG~BZ18BD+}h|GQKc-g^{++3MY>}W_uq7jGHx{mwE9fZiPCoxN$+7 zrODGGJrOkcPQUB(FD5aoS4g~7#6NR^ma7-!>mHuJfY5kTe6PpNNKC9GGRiu^L31uG z$7v`*JknQHsYB!Tm_W{a32TM099djW%5e+j0Ve_ct}IM>XLF1Ap+YvcrLV=|CKo6S zb+9Nl3_YdKP6%Cxy@6TxZ>;4&nTneadr z_ES90ydCev)LV!dN=#(*f}|ZORFdvkYBni^aLbUk>BajeWIOcmHP#8S)*2U~QKI%S zyrLmtPqb&TphJ;>yAxri#;{uyk`JJqODDw%(Z=2`1uc}br^V%>j!gS)D*q*f_-qf8&D;W1dJgQMlaH5er zN2U<%Smb7==vE}dDI8K7cKz!vs^73o9f>2sgiTzWcwY|BMYHH5%Vn7#kiw&eItCqa zIkR2~Q}>X=Ar8W|^Ms41Fm8o6IB2_j60eOeBB1Br!boW7JnoeX6Gs)?7rW0^5psc- zjS16yb>dFn>KPOF;imD}e!enuIniFzv}n$m2#gCCv4jM#ArwlzZ$7@9&XkFxZ4n!V zj3dyiwW4Ki2QG{@i>yuZXQizw_OkZI^-3otXC{!(lUpJF33gI60ak;Uqitp74|B6I zgg{b=Iz}WkhCGj1M=hu4#Aw173YxIVbISaoc z-nLZC*6Tgivd5V`K%GxhBsp@SUU60-rfc$=wb>zdJzXS&-5(NRRodFk;Kxk!S(O(a0e7oY=E( zAyS;Ow?6Q&XA+cnkCb{28_1N8H#?J!*$MmIwLq^*T_9-z^&UE@A(z9oGYtFy6EZef LrJugUA?W`A8`#=m diff --git a/src/actions/auth.ts b/src/actions/auth.ts index 2da04c6..b136f7c 100644 --- a/src/actions/auth.ts +++ b/src/actions/auth.ts @@ -1,26 +1,14 @@ -import { AxiosResponse } from 'axios'; -import { apiClient } from '../lib/apiClient'; +import { apiRequest } from './helpers'; -export const getAuth = (locale: string, data: { login: string, password: string }): Promise> => ( - apiClient.post( - '/auth/login', - data, - { - headers: { - 'X-User-Language': locale - } - } - ) -); +export const getAuth = (locale: string, data: { login: string, password: string }): Promise<{ jwtToken: string }> => apiRequest({ + url: '/auth/login', + method: 'post', + data, + locale +}); -export const getRegister = (locale: string): Promise> => ( - apiClient.post( - '/auth/register', - {}, - { - headers: { - 'X-User-Language': locale - } - } - ) -); +export const getRegister = (locale: string): Promise<{ jwtToken: string }> => apiRequest({ + url: '/auth/register', + method: 'post', + locale +}); diff --git a/src/actions/experts.ts b/src/actions/experts.ts index 91e0d73..93aa3fa 100644 --- a/src/actions/experts.ts +++ b/src/actions/experts.ts @@ -1,30 +1,16 @@ -import { apiClient } from '../lib/apiClient'; import { GeneralFilter, ExpertsData, ExpertDetails } from '../types/experts'; +import { apiRequest } from './helpers'; -export const getExpertsList = async (locale: string, filter?: GeneralFilter) => { - const response = await apiClient.post( - '/home/coachsearch1', - { ...filter }, - { - headers: { - 'X-User-Language': locale - } - } - ); +export const getExpertsList = (locale: string, filter?: GeneralFilter): Promise => apiRequest({ + url: '/home/coachsearch1', + method: 'post', + data: { ...filter }, + locale +}); - return response.data as ExpertsData || null; -}; - -export const getExpertById = async (id: string, locale: string) => { - const response = await apiClient.post( - '/home/coachdetails', - { id }, - { - headers: { - 'X-User-Language': locale - } - } - ); - - return response.data as ExpertDetails || null; -}; +export const getExpertById = (id: string, locale: string): Promise => apiRequest({ + url: '/home/coachdetails', + method: 'post', + data: { id }, + locale +}); diff --git a/src/actions/helpers.ts b/src/actions/helpers.ts new file mode 100644 index 0000000..d3841c5 --- /dev/null +++ b/src/actions/helpers.ts @@ -0,0 +1,43 @@ +import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; +import { apiClient } from '../lib/apiClient'; + +type RequiredConfigParams = Required> & Pick, 'data'>; +export type PageRequestConfig = RequiredConfigParams & { locale?: string, token?: string }; + +export const apiRequest = async ( + baseParams: PageRequestConfig, +): Promise => { + try { + const config = { + url: baseParams.url, + method: baseParams.method, + data: baseParams?.data, + headers: { + 'X-User-Language': baseParams?.locale || 'en', + 'X-Referrer-Channel': 'site', + ...(baseParams?.token ? { Authorization: `Bearer ${baseParams.token}` } : {}) + } + }; + const response: AxiosResponse = await apiClient.request, T>(config as AxiosRequestConfig); + + return response.data; + } catch (err) { + const { + response: { + status: responseCode = null, + statusText = '', + data: { message = '', status: errorKey = '' } = {}, + } = {}, + code: statusCode = '', + } = err as AxiosError; + + throw new Error( + JSON.stringify({ + statusCode, + statusMessage: message || statusText, + responseCode, + errorKey, + }), + ); + } +}; diff --git a/src/actions/hooks/useProfileSettings.ts b/src/actions/hooks/useProfileSettings.ts index 14a67d1..58b02e8 100644 --- a/src/actions/hooks/useProfileSettings.ts +++ b/src/actions/hooks/useProfileSettings.ts @@ -15,7 +15,7 @@ export const useProfileSettings = (locale: string) => { useEffect(() => { if (jwt) { getPersonalData(locale, jwt) - .then(({ data }) => { + .then((data) => { setProfileSettings(data); }) .catch((err) => { diff --git a/src/actions/hooks/useSessionDetails.ts b/src/actions/hooks/useSessionDetails.ts index b1c7bb0..107590d 100644 --- a/src/actions/hooks/useSessionDetails.ts +++ b/src/actions/hooks/useSessionDetails.ts @@ -18,8 +18,8 @@ export const useSessionDetails = (locale: string, sessionId: number) => { setSession(undefined); getSessionDetails(locale, jwt, sessionId) - .then(({ data }) => { - setSession(data); + .then((session) => { + setSession(session); }) .catch((err) => { setErrorData(err); diff --git a/src/actions/profile.ts b/src/actions/profile.ts index 8e78fca..d2c9d96 100644 --- a/src/actions/profile.ts +++ b/src/actions/profile.ts @@ -1,29 +1,17 @@ -import { AxiosResponse } from 'axios'; -import { apiClient } from '../lib/apiClient'; import { Profile } from '../types/profile'; +import { apiRequest } from './helpers'; -export const setPersonData = (person: { login: string, password: string, role: string, languagesLinks: any[] }, locale: string, jwt: string): Promise> => ( - apiClient.post( - '/home/applyperson1', - { ...person }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const setPersonData = (data: { login: string, password: string, role: string, languagesLinks: any[] }, locale: string, token: string): Promise<{ userData: Profile }> => apiRequest({ + url: '/home/applyperson1', + method: 'post', + data, + locale, + token +}); -export const getPersonalData = (locale: string, jwt: string): Promise> => ( - apiClient.post( - '/home/userdata', - {}, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const getPersonalData = (locale: string, token: string): Promise => apiRequest({ + url: '/home/userdata', + method: 'post', + locale, + token +}); diff --git a/src/actions/sessions.ts b/src/actions/sessions.ts index d1aac29..3ae9872 100644 --- a/src/actions/sessions.ts +++ b/src/actions/sessions.ts @@ -1,145 +1,93 @@ -import { AxiosResponse } from 'axios'; -import { apiClient } from '../lib/apiClient'; import { DeclineSessionData, Session, SessionsFilter, SessionCommentData } from '../types/sessions'; +import { apiRequest } from './helpers'; -export const getUpcomingSessions = (locale: string, jwt: string, filter?: SessionsFilter): Promise> => ( - apiClient.post( - '/home/upcomingsessionsall', - { - sessionType: 'session', - ...(filter || {}) - }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const getUpcomingSessions = (locale: string, token: string, filter?: SessionsFilter): Promise => apiRequest({ + url: '/home/upcomingsessionsall', + method: 'post', + data: { + sessionType: 'session', + ...(filter || {}) + }, + locale, + token +}); -export const getRequestedSessions = (locale: string, jwt: string): Promise> => ( - apiClient.post( - '/home/coachhomedata', - {}, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const getRequestedSessions = (locale: string, token: string): Promise<{ requestedSessions: Session[] }> => apiRequest({ + url: '/home/coachhomedata', + method: 'post', + locale, + token +}); -export const getRecentSessions = (locale: string, jwt: string, filter?: SessionsFilter): Promise> => ( - apiClient.post( - '/home/historicalmeetings', - { - sessionType: 'session', - ...(filter || {}) - }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const getRecentSessions = (locale: string, token: string, filter?: SessionsFilter): Promise => apiRequest({ + url: '/home/historicalmeetings', + method: 'post', + data: { + sessionType: 'session', + ...(filter || {}) + }, + locale, + token +}); -export const getSessionDetails = (locale: string, jwt: string, id: number): Promise> => ( - apiClient.post( - '/home/session', - { id }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const getSessionDetails = (locale: string, token: string, id: number): Promise => apiRequest({ + url: '/home/session', + method: 'post', + data: { id }, + locale, + token +}); -export const approveRequestedSession = (locale: string, jwt: string, sessionId: number): Promise => ( - apiClient.post( - '/home/approverequestedsession', - { sessionId }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const approveRequestedSession = (locale: string, token: string, sessionId: number): Promise => apiRequest({ + url: '/home/approverequestedsession', + method: 'post', + data: { sessionId }, + locale, + token +}); -export const declineRequestedSession = (locale: string, jwt: string, { sessionId, reason }: DeclineSessionData): Promise => ( - apiClient.post( - '/home/declinerequestedsession', - { - sessionId, - coachDeclineReason: reason - }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const declineRequestedSession = (locale: string, token: string, { sessionId, reason }: DeclineSessionData): Promise => apiRequest({ + url: '/home/declinerequestedsession', + method: 'post', + data: { + sessionId, + coachDeclineReason: reason + }, + locale, + token +}); -export const cancelUpcomingSession = (locale: string, jwt: string, { sessionId, reason }: DeclineSessionData): Promise => ( - apiClient.post( - '/home/cancelupcomingsession', - { - sessionId, - coachCancelReason: reason - }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const cancelUpcomingSession = (locale: string, token: string, { sessionId, reason }: DeclineSessionData): Promise => apiRequest({ + url: '/home/cancelupcomingsession', + method: 'post', + data: { + sessionId, + coachCancelReason: reason + }, + locale, + token +}); -export const addSessionComment = (locale: string, jwt: string, data: SessionCommentData): Promise => ( - apiClient.post( - '/home/session_comment', - data, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const addSessionComment = (locale: string, token: string, data: SessionCommentData): Promise => apiRequest({ + url: '/home/session_comment', + method: 'post', + data, + locale, + token +}); -export const trackingStartSession = (locale: string, jwt: string, id: number): Promise => ( - apiClient.post( - '/home/sessiontracking', - { id }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const trackingStartSession = (locale: string, token: string, id: number): Promise => apiRequest({ + url: '/home/sessiontracking', + method: 'post', + data: { id }, + locale, + token +}); -export const finishSession = (locale: string, jwt: string, sessionId: number): Promise => ( - apiClient.post( - '/home/finishsession', - { sessionId }, - { - headers: { - 'X-User-Language': locale, - Authorization: `Bearer ${jwt}` - } - } - ) -); +export const finishSession = (locale: string, token: string, sessionId: number): Promise => apiRequest({ + url: '/home/finishsession', + method: 'post', + data: { sessionId }, + locale, + token +}); diff --git a/src/actions/tags.ts b/src/actions/tags.ts index 7443607..7fd18cd 100644 --- a/src/actions/tags.ts +++ b/src/actions/tags.ts @@ -1,29 +1,14 @@ -import { apiClient } from '../lib/apiClient'; import { SearchData, Languages } from '../types/tags'; +import { apiRequest } from './helpers'; -export const getTagList = async (locale: string) => { - const response = await apiClient.post( - '/home/searchdata', - {}, - { - headers: { - 'X-User-Language': locale - } - } - ); +export const getTagList = (locale: string): Promise => apiRequest({ + url: '/home/searchdata', + method: 'post', + locale +}); - return response.data as SearchData || null; -}; - -export const getLanguages = async (locale: string) => { - const response = await apiClient.get( - '/home/languages', - { - headers: { - 'X-User-Language': locale - } - } - ); - - return response.data as Languages || null; -}; +export const getLanguages = (locale: string): Promise => apiRequest({ + url: '/home/languages', + method: 'get', + locale +}); diff --git a/src/app/[locale]/(main)/@news/page.tsx b/src/app/[locale]/(main)/@news/page.tsx index 15bce46..2aa2a09 100644 --- a/src/app/[locale]/(main)/@news/page.tsx +++ b/src/app/[locale]/(main)/@news/page.tsx @@ -1,10 +1,16 @@ import React from 'react'; +import { useTranslations } from 'next-intl'; +import { unstable_setRequestLocale } from 'next-intl/server'; +import { i18nText } from '../../../../i18nKeys'; + +export default function News({ params: { locale } }: { params: { locale: string }}) { + unstable_setRequestLocale(locale); + const t = useTranslations('Main'); -export default function News() { return (
-

Professional Articles & Project News

+

{t('news')}

@@ -18,7 +24,7 @@ export default function News() { performance from many angles, such as human resources management, IT, operations management, risks etc.
- Read more + {i18nText('readMore', locale)}
@@ -36,7 +42,7 @@ export default function News() { performance from many angles, such as human resources management, IT, operations management, risks etc.
- Read more + {i18nText('readMore', locale)}
@@ -54,7 +60,7 @@ export default function News() { performance from many angles, such as human resources management, IT, operations management, risks etc.
- Read more + {i18nText('readMore', locale)} diff --git a/src/app/[locale]/(main)/layout.tsx b/src/app/[locale]/(main)/layout.tsx index daa1e7d..a22926a 100644 --- a/src/app/[locale]/(main)/layout.tsx +++ b/src/app/[locale]/(main)/layout.tsx @@ -12,17 +12,15 @@ import React, { ReactNode } from 'react'; // }; // } -export default function MainLayout({ children, news, directions, experts }: { +export default function MainLayout({ children, news, experts }: { children: ReactNode, news: ReactNode, - directions: ReactNode, experts: ReactNode }) { return ( <> {children} {news} - {directions} {experts} ); diff --git a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx b/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx index 9b8fd55..2b00727 100644 --- a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx +++ b/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx @@ -1,7 +1,11 @@ import React from 'react'; +import { unstable_setRequestLocale } from 'next-intl/server'; import { Link } from '../../../../../../navigation'; +import { i18nText } from '../../../../../../i18nKeys'; + +export default function Coaching({ params: { locale } }: { params: { locale: string } }) { + unstable_setRequestLocale(locale); -export default function Coaching() { return ( <>
    @@ -94,9 +98,7 @@ export default function Coaching() {
    -

    - Professional Certification -

    +

    {i18nText('profCertification', locale)}

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra diff --git a/src/app/[locale]/account/(account)/work-with-us/page.tsx b/src/app/[locale]/account/(account)/work-with-us/page.tsx index f55ba69..465b5a0 100644 --- a/src/app/[locale]/account/(account)/work-with-us/page.tsx +++ b/src/app/[locale]/account/(account)/work-with-us/page.tsx @@ -1,30 +1,24 @@ import React from 'react'; -import type { Metadata } from 'next'; import { unstable_setRequestLocale } from 'next-intl/server'; -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({ params: { locale } }: { params: { locale: string } }) { unstable_setRequestLocale(locale); - const t = useTranslations('Account.WorkWithUs'); return ( <>
    1. {i18nText('accountMenu.work-with-us', locale)}
    -
    +
    -
    {i18nText('insertInfo', locale)}
    - -
    {i18nText('changeUserData', locale)}
    +
    +
    {i18nText('insertInfo', locale)}
    +
    {i18nText('changeUserData', locale)}
    + +
    ); diff --git a/src/app/[locale]/experts/[expertId]/page.tsx b/src/app/[locale]/experts/[expertId]/page.tsx index 6d1a701..8a19c42 100644 --- a/src/app/[locale]/experts/[expertId]/page.tsx +++ b/src/app/[locale]/experts/[expertId]/page.tsx @@ -11,6 +11,7 @@ import { } from '../../../../components/Experts/ExpertDetails'; import { Details } from '../../../../types/experts'; import { BackButton } from '../../../../components/view/BackButton'; +import { i18nText } from '../../../../i18nKeys'; export const metadata: Metadata = { title: 'Bbuddy - Experts item', @@ -31,10 +32,11 @@ export async function generateStaticParams({ return result; } -export default async function ExpertItem({ params: { expertId = '', locale} }: { params: { expertId: string, locale: string } }) { +export default async function ExpertItem({ params: { expertId = '', locale } }: { params: { expertId: string, locale: string } }) { if (!expertId) notFound(); const expert = await getExpertById(expertId, locale); + console.log(expert); const getAssociationLevel = (accLevelId?: number) => { if (accLevelId) { @@ -75,16 +77,16 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: {
    - + - Back to experts list + {i18nText('backToExperts', locale)}
    - + -

    Expert Background

    +

    {i18nText('expertBackground', locale)}

    Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra malesuada, ligula sem tempor risus, non posuere urna diam a libero. @@ -92,7 +94,7 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: { {expert?.publicCoachDetails?.educations && expert.publicCoachDetails.educations?.map(generateDescription)} {expert?.publicCoachDetails?.certificates && expert.publicCoachDetails.certificates.length > 0 && (

    -

    Professional Certification

    +

    {i18nText('profCertification', locale)}

    {expert.publicCoachDetails.certificates?.map((cert) => (

    @@ -110,7 +112,7 @@ export default async function ExpertItem({ params: { expertId = '', locale} }: { {expert?.publicCoachDetails?.trainings && expert.publicCoachDetails.trainings?.map(generateDescription)} {expert?.publicCoachDetails?.mbas && expert.publicCoachDetails.mbas?.map(generateDescription)} {expert?.publicCoachDetails?.experiences && expert.publicCoachDetails.experiences?.map(generateDescription)} - + {/*

    All Offers by this Expert

    diff --git a/src/components/Account/AccountMenu.tsx b/src/components/Account/AccountMenu.tsx index f7f61af..601a8b7 100644 --- a/src/components/Account/AccountMenu.tsx +++ b/src/components/Account/AccountMenu.tsx @@ -1,20 +1,17 @@ 'use client'; -import React, { useState } from 'react'; import { Button } from 'antd'; 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'; -import { DeleteAccountModal } from '../Modals/DeleteAccountModal'; import { getMenuConfig } from '../../utils/account'; export const AccountMenu = ({ locale }: { locale: string }) => { const selectedLayoutSegment = useSelectedLayoutSegment(); const pathname = selectedLayoutSegment || ''; const paths = usePathname(); - const [showDeleteModal, setShowDeleteModal] = useState(false); const menu: { path: string, title: string, count?: number }[] = getMenuConfig(locale); const onLogout = () => { @@ -23,8 +20,6 @@ export const AccountMenu = ({ locale }: { locale: string }) => { window?.location?.replace(`/${paths.split('/')[1]}/`); }; - const onDeleteAccount = () => setShowDeleteModal(true); - return (
      {menu.map(({ path, title, count }) => ( @@ -46,19 +41,6 @@ export const AccountMenu = ({ locale }: { locale: string }) => { {i18nText('logout', locale)} -
    • - - setShowDeleteModal(false)} - /> -
    ); }; diff --git a/src/components/Account/ProfileSettings.tsx b/src/components/Account/ProfileSettings.tsx index e40ee88..0d6c4a4 100644 --- a/src/components/Account/ProfileSettings.tsx +++ b/src/components/Account/ProfileSettings.tsx @@ -1,15 +1,18 @@ 'use client'; import React, { FC, useEffect, useState } from 'react'; -import { Form, Upload, Button } from 'antd'; +import { Form, Upload } from 'antd'; 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/CustomInput'; +import { CameraOutlined, DeleteOutlined } from '@ant-design/icons'; +import { useRouter } from '../../navigation'; +import { i18nText } from '../../i18nKeys'; import { Profile } from '../../types/profile'; import { useProfileSettings } from '../../actions/hooks/useProfileSettings'; -import { i18nText } from '../../i18nKeys'; +import { CustomInput } from '../view/CustomInput'; +import { OutlinedButton } from '../view/OutlinedButton'; +import { FilledYellowButton } from '../view/FilledButton'; +import { DeleteAccountModal } from "../Modals/DeleteAccountModal"; type ProfileSettingsProps = { locale: string; @@ -20,6 +23,8 @@ type ProfileSettingsProps = { export const ProfileSettings: FC = ({ locale }) => { const [form] = Form.useForm(); const { profileSettings } = useProfileSettings(locale); + const [showDeleteModal, setShowDeleteModal] = useState(false); + const router = useRouter(); useEffect(() => { if (profileSettings) { @@ -27,6 +32,13 @@ export const ProfileSettings: FC = ({ locale }) => { } }, [profileSettings]); + const saveProfileSettings = () => { + form.validateFields() + .then(() => { + console.log('success') + }) + } + const [fileList, setFileList] = useState(); const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { @@ -48,6 +60,8 @@ export const ProfileSettings: FC = ({ locale }) => { // imgWindow?.document.write(image.outerHTML); }; + const onDeleteAccount = () => setShowDeleteModal(true); + return (
    @@ -67,32 +81,45 @@ export const ProfileSettings: FC = ({ locale }) => { */} -
    - - - -
    -
    - - - -
    - {/*
    +
    +
    + + + +
    +
    + + + +
    + {/*
    */} -
    - - - +
    + + + +
    -
    - +
    + {i18nText('save', locale)} + router.push('change-password')}> {i18nText('changePass', locale)} - + + } + danger + > + {i18nText('deleteAcc', locale)} +
    - + setShowDeleteModal(false)} + /> ); }; diff --git a/src/components/Account/sessions/SessionDetailsContent.tsx b/src/components/Account/sessions/SessionDetailsContent.tsx index fb17e54..f714917 100644 --- a/src/components/Account/sessions/SessionDetailsContent.tsx +++ b/src/components/Account/sessions/SessionDetailsContent.tsx @@ -1,19 +1,19 @@ 'use client' -import React, {useState} from 'react'; -import {Button, Empty, notification, Tag} from 'antd'; -import {LeftOutlined, PlusOutlined, RightOutlined} from '@ant-design/icons'; +import React, { useState } from 'react'; +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 {i18nText} from '../../../i18nKeys'; -import {getDuration, getPrice} from '../../../utils/expert'; -import {PublicUser, Session, SessionState, SessionType} from '../../../types/sessions'; -import {AUTH_TOKEN_KEY} from '../../../constants/common'; -import {approveRequestedSession, finishSession} from '../../../actions/sessions'; -import {useLocalStorage} from '../../../hooks/useLocalStorage'; -import {DeclineSessionModal} from '../../Modals/DeclineSessionModal'; -import {AddCommentModal} from '../../Modals/AddCommentModal'; +import { Link, useRouter } from '../../../navigation'; +import { i18nText } from '../../../i18nKeys'; +import { getDuration, getPrice } from '../../../utils/expert'; +import { PublicUser, Session, SessionState, SessionType } from '../../../types/sessions'; +import { AUTH_TOKEN_KEY } from '../../../constants/common'; +import { approveRequestedSession, finishSession } from '../../../actions/sessions'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { DeclineSessionModal } from '../../Modals/DeclineSessionModal'; +import { AddCommentModal } from '../../Modals/AddCommentModal'; type SessionDetailsContentProps = { locale: string; @@ -43,7 +43,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio }) .catch((err) => { notification.error({ - message: 'Error approve session', + message: i18nText('errors.approvingSession', locale), description: err?.response?.data?.errMessage }); }) @@ -64,7 +64,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio }) .catch((err) => { notification.error({ - message: 'Error finish session', + message: i18nText('errors.finishingSession', locale), description: err?.response?.data?.errMessage }); }) @@ -166,7 +166,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio icon={} onClick={goBack} > - Back + {i18nText('back', locale)}
    {Current} @@ -181,8 +181,8 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio disabled={finishLoading} > {activeType === SessionType.UPCOMING - ? (session?.state === SessionState.STARTED ? 'Join Session' : 'Start Session') - : 'Confirm Session'} + ? (session?.state === SessionState.STARTED ? i18nText('session.join', locale) : i18nText('session.start', locale)) + : i18nText('session.confirm', locale)} {session?.state === SessionState.STARTED && isCoach && ( )} {session?.id && session?.state !== SessionState.STARTED && ( @@ -200,7 +200,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio onClick={() => setOpenDeclineModal(true)} disabled={approveLoading} > - Decline Session + {i18nText('session.decline', locale)} {activeType === SessionType.RECENT && ( <> -
    Course Info
    +
    {i18nText('courseInfo', locale)}
    {/*
    {current?.specialityDesc}
    @@ -249,7 +249,9 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio
    - {session?.clientComments?.length === 0 && session?.coachComments?.length === 0 ? 'Comments' : 'My Comments'} + {session?.clientComments?.length === 0 && session?.coachComments?.length === 0 + ? i18nText('session.comments', locale) + : i18nText('session.myComments', locale)}
    {activeType === SessionType.UPCOMING && ( <> @@ -260,7 +262,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio icon={} onClick={() => setOpenAddCommentModal(true)} > - Add new + {i18nText('session.addComment', locale)} { ]) .then(([upcoming, requested, recent]) => { setSessions({ - [SessionType.UPCOMING]: upcoming.data || [], - [SessionType.REQUESTED]: requested.data?.requestedSessions || [], - [SessionType.RECENT]: recent.data || [] + [SessionType.UPCOMING]: upcoming || [], + [SessionType.REQUESTED]: requested?.requestedSessions || [], + [SessionType.RECENT]: recent || [] }); }) .catch((err) => { @@ -115,7 +115,7 @@ export const SessionsTabs = ({ locale, activeTab }: SessionsTabsProps) => {
    ) }) : ( - + )}
    diff --git a/src/components/Experts/ExpertDetails.tsx b/src/components/Experts/ExpertDetails.tsx index 0c06c74..4398016 100644 --- a/src/components/Experts/ExpertDetails.tsx +++ b/src/components/Experts/ExpertDetails.tsx @@ -7,13 +7,15 @@ import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons'; import { ExpertDetails, ExpertDocument } from '../../types/experts'; import { Locale } from '../../types/locale'; import { CustomRate } from '../view/CustomRate'; +import { i18nText } from '../../i18nKeys'; +import { FilledYellowButton } from '../view/FilledButton'; type ExpertDetailsProps = { expert: ExpertDetails; locale?: string; }; -export const ExpertCard: FC = ({ expert }) => { +export const ExpertCard: FC = ({ expert, locale }) => { const { publicCoachDetails } = expert || {}; return ( @@ -25,39 +27,43 @@ export const ExpertCard: FC = ({ expert }) => {

    {`${publicCoachDetails?.name} ${publicCoachDetails?.surname || ''}`}

    - {`${publicCoachDetails?.practiceHours} Practice hours`} + {`${publicCoachDetails?.practiceHours} ${i18nText('practiceHours', locale)}`} | - {`${publicCoachDetails?.supervisionPerYearId} Supervision per year`} + {`${publicCoachDetails?.supervisionPerYearId} ${i18nText('supervisionCount', locale)}`}
    } disabled /> - 4/5 (out of 345) + {`4/5 (${i18nText('outOf', locale)} 345)`}
    ); }; export const ExpertInformation: FC = ({ expert, locale }) => { - const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0 } } = expert || {}; + const { publicCoachDetails: { tags = [], sessionCost = 0, sessionDuration = 0, coachLanguages = [] } } = expert || {}; const isRus = locale === Locale.ru; return ( <> -

    Current Offer

    -
    - {tags?.map((skill) => {skill?.name})} +
    + {/*

    {}

    */} +
    + {coachLanguages?.map((skill) => {skill})} +

    Hello, my name is Marcelo. I am a Senior UX Designer with more than 6 years of experience working @@ -74,10 +80,11 @@ export const ExpertInformation: FC = ({ expert, locale }) => Oh, and I also speak Spanish!

    +
    + {tags?.map((skill) => {skill?.name})} +
    - - Sign Up Now - + console.log('schedule')}>{i18nText('signUp', locale)}
    {`${sessionCost}€`} / {`${sessionDuration}${isRus ? 'мин' : 'min'}`}
    @@ -86,7 +93,7 @@ export const ExpertInformation: FC = ({ expert, locale }) => ); }; -export const ExpertPractice: FC = ({ expert }) => { +export const ExpertPractice: FC = ({ expert, locale }) => { const { publicCoachDetails: { practiceCases = [], themesGroups = [] } } = expert || {}; return practiceCases?.length > 0 ? ( @@ -100,7 +107,7 @@ export const ExpertPractice: FC = ({ expert }) => { {themesGroupIds && (
    {filtered?.map(({ id, name }) => ( -
    {name}
    + {name} ))}
    )} diff --git a/src/components/Experts/ExpertsList.tsx b/src/components/Experts/ExpertsList.tsx index f84351a..28db1e5 100644 --- a/src/components/Experts/ExpertsList.tsx +++ b/src/components/Experts/ExpertsList.tsx @@ -84,6 +84,7 @@ export const ExpertsList = ({ size="large" className="search-result" dataSource={experts.coaches} + locale={{ emptyText: i18nText('notFound', locale) }} renderItem={(item) => ( (
    -

    {name}

    +

    {name}

    {getList('themesTagIds', tags)}
    )) : null; @@ -214,7 +214,7 @@ export const ExpertsFilter = ({ key: 'themesTagIds', label: ( <> -
    Direction
    +
    {i18nText('direction', locale)}
    {!openedTabs.includes('themesTagIds') && filter?.themesTagIds?.length > 0 && (
    {getSelectedTags()}
    )} diff --git a/src/components/Modals/AddCommentModal.tsx b/src/components/Modals/AddCommentModal.tsx index 43e4a0c..9fa1d83 100644 --- a/src/components/Modals/AddCommentModal.tsx +++ b/src/components/Modals/AddCommentModal.tsx @@ -83,7 +83,7 @@ export const AddCommentModal: FC = ({ rules={[ { required: true, - message: 'Please input your comment' + message: i18nText('errors.emptyComment', locale) } ]} > @@ -91,7 +91,7 @@ export const AddCommentModal: FC = ({ className="b-textarea" rows={4} maxLength={1000} - placeholder="Your comment" + placeholder={i18nText('session.commentPlaceholder', locale)} /> @@ -101,7 +101,7 @@ export const AddCommentModal: FC = ({ onClick={onAddComment} loading={loading} > - Send + {i18nText('send', locale)}
    diff --git a/src/components/Modals/AuthModal.tsx b/src/components/Modals/AuthModal.tsx index b7758a0..034225c 100644 --- a/src/components/Modals/AuthModal.tsx +++ b/src/components/Modals/AuthModal.tsx @@ -6,6 +6,7 @@ import Link from 'next/link'; import { Modal, Form } from 'antd'; import { CloseOutlined } from '@ant-design/icons'; import { RegisterContent, ResetContent, FinishContent, EnterContent } from './authModalContent'; +import { i18nText } from '../../i18nKeys'; type AuthModalProps = { open: boolean; @@ -13,6 +14,7 @@ type AuthModalProps = { mode: 'enter' | 'register' | 'reset' | 'finish'; updateMode: (mode: 'enter' | 'register' | 'reset' | 'finish') => void; updateToken: string | Dispatch> | undefined; + locale: string; }; export const AuthModal: FC = ({ @@ -20,7 +22,8 @@ export const AuthModal: FC = ({ handleCancel, mode, updateMode, - updateToken + updateToken, + locale }) => { const [form] = Form.useForm<{ login: string, password: string, confirmPassword: string }>(); const paths = usePathname().split('/'); @@ -50,7 +53,7 @@ export const AuthModal: FC = ({ onCancel={handleCancel} afterClose={onAfterClose} footer={false} - width={498} + width={598} closeIcon={} >
    @@ -82,8 +85,8 @@ export const AuthModal: FC = ({ )}
    - I have read and agree with the terms of the - User Agreement, Privacy Policy + {`${i18nText('agreementText', locale)} `} + {i18nText('privacyPolicy', locale)}
    diff --git a/src/components/Modals/DeclineSessionModal.tsx b/src/components/Modals/DeclineSessionModal.tsx index 44f23b4..ab6f508 100644 --- a/src/components/Modals/DeclineSessionModal.tsx +++ b/src/components/Modals/DeclineSessionModal.tsx @@ -7,7 +7,7 @@ import { SessionType } from '../../types/sessions'; import { AUTH_TOKEN_KEY } from '../../constants/common'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import { cancelUpcomingSession, declineRequestedSession } from '../../actions/sessions'; -// import { i18nText } from '../../i18nKeys'; +import { i18nText } from '../../i18nKeys'; import { FilledButton } from '../view/FilledButton'; type DeclineModalProps = { @@ -79,7 +79,7 @@ export const DeclineSessionModal: FC = ({
    - Enter a reason for cancelling the session + {i18nText('session.cancelReason', locale)}
    = ({ rules={[ { required: true, - message: 'Please input the reason' + message: i18nText('errors.emptyCancelReason', locale) } ]} >
    @@ -106,7 +106,7 @@ export const DeclineSessionModal: FC = ({ onClick={onDecline} loading={loading} > - Decline + {i18nText('decline', locale)}
    diff --git a/src/components/Modals/authModalContent/EnterContent.tsx b/src/components/Modals/authModalContent/EnterContent.tsx index 2bc79c7..004a776 100644 --- a/src/components/Modals/authModalContent/EnterContent.tsx +++ b/src/components/Modals/authModalContent/EnterContent.tsx @@ -37,10 +37,10 @@ export const EnterContent: FC = ({ const { login, password } = form.getFieldsValue(); setIsLoading(true); getAuth(locale, { login, password }) - .then(({ data }) => { + .then((data) => { if (data.jwtToken) { getPersonalData(locale, data.jwtToken) - .then(({ data: profile }) => { + .then((profile) => { localStorage.setItem(AUTH_USER, JSON.stringify(profile)); updateToken(data.jwtToken); handleCancel(); @@ -110,11 +110,11 @@ export const EnterContent: FC = ({ rules={[ { type: 'email', - message: 'The input is not valid E-mail' + message: i18nText('errors.validEmail', locale) }, { required: true, - message: 'Please input your E-mail' + message: i18nText('error.emptyEmail', locale) } ]} > @@ -129,7 +129,7 @@ export const EnterContent: FC = ({ noStyle rules={[{ required: true, - message: 'Please input your password' + message: i18nText('errors.emptyPass', locale) }]} > = ({ type="link" onClick={() => updateMode('reset')} > - Forgot password? + {`${i18nText('forgotPass', locale)}?`} - or + {i18nText('or', locale)} } onClick={() => onSocialEnter(Social.FACEBOOK)} > - Facebook account + {i18nText('facebook', locale)} } onClick={() => onSocialEnter(Social.APPLE)} > - Apple account + {i18nText('apple', locale)} } onClick={() => onSocialEnter(Social.GOOGLE)} > - Google account + {i18nText('google', locale)} ); diff --git a/src/components/Modals/authModalContent/FinishContent.tsx b/src/components/Modals/authModalContent/FinishContent.tsx index 9ed06bb..e8271da 100644 --- a/src/components/Modals/authModalContent/FinishContent.tsx +++ b/src/components/Modals/authModalContent/FinishContent.tsx @@ -5,14 +5,10 @@ import { i18nText } from '../../../i18nKeys'; export const FinishContent = ({ locale }: { locale: string }) => ( <>
    - A link to reset your password has been sent -
    - to your email + {i18nText('resetPassText', locale)}
    - - {i18nText('enter', locale)} + + {i18nText('enterAccount', locale)} ); diff --git a/src/components/Modals/authModalContent/RegisterContent.tsx b/src/components/Modals/authModalContent/RegisterContent.tsx index ce3775e..f62fc59 100644 --- a/src/components/Modals/authModalContent/RegisterContent.tsx +++ b/src/components/Modals/authModalContent/RegisterContent.tsx @@ -36,10 +36,10 @@ export const RegisterContent: FC = ({ const { login, password } = form.getFieldsValue(); setIsLoading(true); getRegister(locale) - .then(({ data }) => { + .then((data) => { if (data.jwtToken) { setPersonData( { login, password, role: 'client', languagesLinks: [] }, locale, data.jwtToken) - .then(({ data: profile }) => { + .then((profile) => { updateToken(data.jwtToken); localStorage.setItem(AUTH_USER, JSON.stringify(profile.userData)); handleCancel(); @@ -115,11 +115,11 @@ export const RegisterContent: FC = ({ rules={[ { type: 'email', - message: 'The input is not valid E-mail' + message: i18nText('errors.validEmail', locale) }, { required: true, - message: 'Please input your E-mail' + message: i18nText('error.emptyEmail', locale) } ]} > @@ -134,7 +134,7 @@ export const RegisterContent: FC = ({ noStyle rules={[{ required: true, - message: 'Please input your password' + message: i18nText('errors.emptyPass', locale) }]} > = ({ rules={[ { required: true, - message: 'Please confirm your password', + message: i18nText('errors.confirmPass', locale), }, ({ getFieldValue }) => ({ validator(_, value) { if (!value || getFieldValue('password') === value) { return Promise.resolve(); } - return Promise.reject(new Error('The new password that you entered do not match')); + return Promise.reject(new Error(i18nText('errors.notMatchPass', locale))); }, }), ]} @@ -176,24 +176,24 @@ export const RegisterContent: FC = ({ {i18nText('registration', locale)} updateMode('enter')}>{i18nText('enter', locale)} - or + {i18nText('or', locale)} } onClick={() => onSocialRegister(Social.FACEBOOK)} > - Facebook account + {i18nText('facebook', locale)} } onClick={() => onSocialRegister(Social.APPLE)} > - Apple account + {i18nText('apple', locale)} } onClick={() => onSocialRegister(Social.GOOGLE)} > - Google account + {i18nText('google', locale)} ); diff --git a/src/components/Modals/authModalContent/ResetContent.tsx b/src/components/Modals/authModalContent/ResetContent.tsx index 6f34207..277d07d 100644 --- a/src/components/Modals/authModalContent/ResetContent.tsx +++ b/src/components/Modals/authModalContent/ResetContent.tsx @@ -29,11 +29,11 @@ export const ResetContent: FC = ({ rules={[ { type: 'email', - message: 'The input is not valid E-mail' + message: i18nText('errors.validEmail', locale) }, { required: true, - message: 'Please input your E-mail' + message: i18nText('error.emptyEmail', locale) } ]} > @@ -48,7 +48,7 @@ export const ResetContent: FC = ({ type="primary" onClick={onResetPassword} > - Reset Password + {i18nText('resetPass', locale)} (
    -
    +

    {title}

    {description &&

    {description}

    } diff --git a/src/components/Page/Header/HeaderAuthLinks.tsx b/src/components/Page/Header/HeaderAuthLinks.tsx index 362dfbe..d8e3d5e 100644 --- a/src/components/Page/Header/HeaderAuthLinks.tsx +++ b/src/components/Page/Header/HeaderAuthLinks.tsx @@ -72,6 +72,7 @@ function HeaderAuthLinks ({ mode={mode} updateMode={setMode} updateToken={setToken} + locale={locale} /> ); diff --git a/src/components/Page/Header/HeaderMenu.tsx b/src/components/Page/Header/HeaderMenu.tsx index 62eae81..a33af1d 100644 --- a/src/components/Page/Header/HeaderMenu.tsx +++ b/src/components/Page/Header/HeaderMenu.tsx @@ -18,6 +18,7 @@ export const HeaderMenu = ({ }: HeaderMenuProps) => { const selectedLayoutSegment = useSelectedLayoutSegment(); const pathname = selectedLayoutSegment || ''; + const url = pathname === '(main)' ? '' : pathname; return (
    @@ -25,7 +26,7 @@ export const HeaderMenu = ({
      {linkConfig.map(({ path, title }) => (
    • - {title} + {title}
    • ))} diff --git a/src/components/Page/Header/index.tsx b/src/components/Page/Header/index.tsx index 6752313..25cbf09 100644 --- a/src/components/Page/Header/index.tsx +++ b/src/components/Page/Header/index.tsx @@ -13,7 +13,7 @@ type HeaderProps = { export const Header: FC = ({ locale }) => { const routes: { path: string, title: string }[] = HEAD_ROUTES.map((item) => ({ path: item, - title: i18nText(`menu.${item}`, locale) + title: i18nText(item ? `menu.${item}` : 'menu.home', locale) })); return ( diff --git a/src/components/view/FilledButton.tsx b/src/components/view/FilledButton.tsx index 4f42a78..3b875b6 100644 --- a/src/components/view/FilledButton.tsx +++ b/src/components/view/FilledButton.tsx @@ -6,3 +6,9 @@ export const FilledButton = (props: any) => ( {props.children} ); + +export const FilledYellowButton = (props: any) => ( + +); diff --git a/src/components/view/OutlinedButton.tsx b/src/components/view/OutlinedButton.tsx index 04bb269..4f23ebb 100644 --- a/src/components/view/OutlinedButton.tsx +++ b/src/components/view/OutlinedButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button } from 'antd'; export const OutlinedButton = (props: any) => ( - ); diff --git a/src/constants/routes.ts b/src/constants/routes.ts index 125bafe..52fb200 100644 --- a/src/constants/routes.ts +++ b/src/constants/routes.ts @@ -1 +1,2 @@ -export const HEAD_ROUTES = ['bb-client', 'bb-expert', 'blog']; +// export const HEAD_ROUTES = ['bb-client', 'bb-expert', 'blog']; +export const HEAD_ROUTES = ['', 'blog']; diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index 6d38422..21bb176 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Mit BB wachsen', 'bb-expert': 'Werde BB-Experte', + home: 'Startseite', blog: 'Blog&News' }, registration: 'Registrieren', enter: 'Anmelden', + enterAccount: 'Konto anmelden', account: 'Mein Konto', logout: 'Abmelden', + decline: 'Ablehnen', + send: 'Senden', deleteAcc: 'Konto löschen', footer: { faq: 'FAQ', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Kommende Sitzungen', requested: 'Angefragte Sitzungen', - recent: 'Letzte Sitzungen' + recent: 'Letzte Sitzungen', + cancelReason: 'Gib einen Grund für die Absage der Sitzung ein', + reasonPlaceholder: 'Beschreibe den Grund für die Ablehnung', + decline: 'Sitzung ablehnen', + confirm: 'Sitzung bestätigen', + join: 'Sitzung beitreten', + start: 'Sitzung starten', + finish: 'Sitzung abschließen', + comments: 'Kommentare', + myComments: 'Meine Kommentare', + addComment: 'Neuen Kommentar hinzufügen', + commentPlaceholder: 'Ihr Kommentar', }, + room: { + upcoming: 'Zukünftige Räume', + requested: 'Angeforderte Räume', + recent: 'Kürzliche Räume', + newRoom: 'Neuer Raum' + }, + agreementText: 'Folgendes habe ich gelesen und erkläre mich damit einverstanden: Benutzervereinbarung,', + userAgreement: 'Benutzervereinbarung', + privacyPolicy: 'Datenschutzrichtlinie', + readMore: 'Mehr erfahren', photoDesc: 'Füge ein echtes Foto hinzu, mit Gesicht wirkt es immer glaubwürdiger.', dayStart: 'Tagesbeginn', topic: 'Thema', @@ -37,6 +62,12 @@ export default { oldPass: 'Altes Passwort', newPass: 'Neues Passwort', confirmPass: 'Passwort bestätigen', + forgotPass: 'Passwort vergessen', + resetPassText: 'Ein Link zum Zurücksetzen Ihres Passworts wurde an Ihre E-Mail gesendet', + or: 'oder', + facebook: 'Facebook-Konto', + apple: 'Apple-Konto', + google: 'Google-Konto', 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', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'Nach Preis absteigend', details: 'Details', sessionLang: 'Sitzungssprache', + direction: 'Wegbeschreibung', fromTo: 'von $ bis $', apply: 'Anwenden', save: 'Speichern', changePass: 'Passwort ändern', + resetPass: 'Passwort zurücksetzen', getStarted: 'Loslegen', delete: 'Löschen', - today: 'Heute' + today: 'Heute', + back: 'Zurück', + backToExperts: 'Zurück zur Expertenliste', + courseInfo: 'Kursinfo', + expertBackground: 'Expertenhintergrund', + profCertification: 'Professionelle Zertifizierung', + practiceHours: 'Praxisstunden', + supervisionCount: 'Supervision pro Jahr', + outOf: 'von', + schedule: 'Zeitplan', + signUp: 'Jetzt anmelden', + noData: 'Keine Daten', + notFound: 'Nicht gefunden', + errors: { + invalidEmail: 'Die E-Mail-Adresse ist ungültig', + emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', + emptyPass: 'Bitte geben Sie Ihr Passwort ein', + confirmPass: 'Bitte bestätigen Sie Ihr Passwort', + notMatchPass: 'Die neuen Passwörter stimmen nicht überein', + emptyCancelReason: 'Bitte gib den Grund ein', + approvingSession: 'Fehler beim Genehmigen der Sitzung', + finishingSession: 'Fehler beim Beenden der Sitzung', + emptyComment: 'Bitte geben Sie Ihren Kommentar ein', + }, } diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index cb90828..cc538c3 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Start grow with BB', 'bb-expert': 'Become BB Expert', + home: 'Home', blog: 'Blog&News' }, registration: 'Registration', enter: 'Enter', + enterAccount: 'Enter account', account: 'My Account', logout: 'Log out', + decline: 'Decline', + send: 'Send', deleteAcc: 'Delete account', footer: { faq: 'FAQ', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Upcoming Sessions', requested: 'Sessions Requested', - recent: 'Recent Sessions' + recent: 'Recent Sessions', + cancelReason: 'Enter a reason for cancelling the session', + reasonPlaceholder: 'Describe the reason for the rejection', + decline: 'Decline session', + confirm: 'Confirm session', + join: 'Join session', + start: 'Start session', + finish: 'Finish session', + comments: 'Comments', + myComments: 'My comments', + addComment: 'Add new', + commentPlaceholder: 'Your comment', }, + room: { + upcoming: 'Upcoming Rooms', + requested: 'Rooms Requested', + recent: 'Recent Rooms', + newRoom: 'New Room' + }, + agreementText: 'I have read and agree with the terms of the User Agreement,', + userAgreement: 'User Agreement', + privacyPolicy: 'Privacy Policy', + readMore: 'Read more', photoDesc: 'Add a real photo, as a person\'s face is always more credible.', dayStart: 'Day start', topic: 'Topic', @@ -37,6 +62,12 @@ export default { oldPass: 'Old Password', newPass: 'New Password', confirmPass: 'Confirm Password', + forgotPass: 'Forgot password', + resetPassText: 'A link to reset your password has been sent to your email', + or: 'or', + facebook: 'Facebook account', + apple: 'Apple account', + google: 'Google account', 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', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'By price descending', details: 'Details', sessionLang: 'Session Language', + direction: 'Direction', fromTo: 'from $ to $', apply: 'Apply', save: 'Save', changePass: 'Change password', + resetPass: 'Reset password', getStarted: 'Get started', delete: 'Delete', - today: 'Today' + today: 'Today', + back: 'Back', + backToExperts: 'Back to experts list', + courseInfo: 'Course Info', + expertBackground: 'Expert Background', + profCertification: 'Professional Certification', + practiceHours: 'Practice hours', + supervisionCount: 'Supervision per year', + outOf: 'out of', + schedule: 'Schedule', + signUp: 'Sign up now', + noData: 'No data', + notFound: 'Not found', + errors: { + invalidEmail: 'The email address is not valid', + emptyEmail: 'Please enter your E-mail', + emptyPass: 'Please enter your password', + confirmPass: 'Please confirm your password', + notMatchPass: 'The new passwords you entered do not match', + emptyCancelReason: 'Please enter the reason', + approvingSession: 'Error approving session', + finishingSession: 'Error finishing session', + emptyComment: 'Please enter your comment', + }, } diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index dac42c9..9c7d096 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Empieza a crecer con BB', 'bb-expert': 'Conviértete en un experto en BB', + home: 'Inicio', blog: 'Blog y noticias' }, registration: 'Registro', enter: 'Entrar', + enterAccount: 'Introducir cuenta', account: 'Mi cuenta', logout: 'Cerrar sesión', + decline: 'Rechazar', + send: 'Enviar', deleteAcc: 'Eliminar cuenta', footer: { faq: 'Preguntas frecuentes', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Próximas sesiones', requested: 'Sesiones solicitadas', - recent: 'Sesiones recientes' + recent: 'Sesiones recientes', + cancelReason: 'Introduce el motivo por el que has cancelado la sesión', + reasonPlaceholder: 'Describe el motivo del rechazo', + decline: 'Rechazar sesión', + confirm: 'Confirmar sesión', + join: 'Unirse a la sesión', + start: 'Iniciar sesión', + finish: 'Finalizar la sesión', + comments: 'Comentarios', + myComments: 'Mis comentarios', + addComment: 'Añadir nuevo comentario', + commentPlaceholder: 'Tu comentario', }, + room: { + upcoming: 'Próximas salas', + requested: 'Salas solicitadas', + recent: 'Salas recientes', + newRoom: 'Nueva sala' + }, + agreementText: 'He leído y acepto las condiciones del Acuerdo de usuario,', + userAgreement: 'Acuerdo de usuario', + privacyPolicy: 'Política de privacidad', + readMore: 'Seguir leyendo', 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', @@ -37,6 +62,12 @@ export default { oldPass: 'Contraseña antigua', newPass: 'Nueva contraseña', confirmPass: 'Confirmar contraseña', + forgotPass: 'Se te ha olvidado la contraseña', + resetPassText: 'Se ha enviado un enlace para restablecer la contraseña a tu correo electrónico', + or: 'o', + facebook: 'Cuenta de Facebook', + apple: 'Cuenta de Apple', + google: 'Cuenta de Google', 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', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'Por precio descendiente', details: 'Detalles', sessionLang: 'Idioma de la sesión', + direction: 'Dirección', fromTo: 'de $ a $', apply: 'Solicitar', save: 'Guardar', changePass: 'Cambiar contraseña', + resetPass: 'Restablecer contraseña', getStarted: 'Empieza', delete: 'Eliminar', - today: 'Hoy día' + today: 'Hoy', + back: 'Volver', + backToExperts: 'Volver a la lista de expertos', + courseInfo: 'Información del curso', + expertBackground: 'Antecedentes del experto', + profCertification: 'Certificación profesional', + practiceHours: 'horas de práctica', + supervisionCount: 'supervisiones anuales', + outOf: 'de', + schedule: 'Horario', + signUp: 'Regístrate ahora', + noData: 'Sin datos', + notFound: 'No encontrado', + errors: { + invalidEmail: 'La dirección de correo electrónico no es válida', + emptyEmail: 'Introduce tu correo electrónico', + emptyPass: 'Introduce tu contraseña', + confirmPass: 'Confirma tu contraseña', + notMatchPass: 'Las nuevas contraseñas que has introducido no coinciden', + emptyCancelReason: 'Introduce el motivo', + approvingSession: 'Error al aprobar la sesión', + finishingSession: 'Error al finalizar la sesión', + emptyComment: 'Introduce tu comentario', + }, } diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index e1afbf6..9b5c471 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Commencez à vous développer avec BB', 'bb-expert': 'Devenez Expert BB', + home: 'Accueil', blog: 'Blog et actus' }, registration: 'Inscription', enter: 'Saisir', + enterAccount: 'Saisir le compte', account: 'Mon compte', logout: 'Déconnexion', + decline: 'Refuser', + send: 'Envoyer', deleteAcc: 'Supprimer le compte', footer: { faq: 'FAQ', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Prochaines sessions', requested: 'Sessions demandées', - recent: 'Sessions récentes' + recent: 'Sessions récentes', + cancelReason: 'Saisissez une raison pour l\'annulation de la session', + reasonPlaceholder: 'Décrivez la raison du refus', + decline: 'Refuser la session', + confirm: 'Confirmer la session', + join: 'Rejoindre la session', + start: 'Commencer la session', + finish: 'Terminer la session', + comments: 'Commentaires', + myComments: 'Mes commentaires', + addComment: 'Ajouter un nouveau commentaire', + commentPlaceholder: 'Votre commentaire', }, + room: { + upcoming: 'Salles futures', + requested: 'Salles demandées', + recent: 'Salles récentes', + newRoom: 'Nouvelle salle' + }, + agreementText: 'J\'ai lu et j\'accepte les dispositions de l\'Accord Utilisateur et de la', + userAgreement: '', + privacyPolicy: 'Politique de Confidentialité', + readMore: 'En savoir plus', 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', @@ -37,6 +62,12 @@ export default { oldPass: 'Ancien mot de passe', newPass: 'Nouveau mot de passe', confirmPass: 'Confirmer le mot de passe', + forgotPass: 'Mot de passe oublié', + resetPassText: 'Un lien pour réinitialiser votre mot de passe a été envoyé à votre adresse e-mail', + or: 'ou', + facebook: 'Compte Facebook', + apple: 'Compte Apple', + google: 'Compte Google', 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', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'Par prix décroissant', details: 'Détails', sessionLang: 'Langue de la session', + direction: 'Direction', fromTo: 'de $ à $', apply: 'Appliquer', save: 'Sauvegarder', changePass: 'Modifier le mot de passe', + resetPass: 'Réinitialiser le mot de passe', getStarted: 'Commencer', delete: 'Supprimer', - today: 'Ce jour' + today: 'Aujourd\'hui', + back: 'Retour', + backToExperts: 'Retour à la liste d\'experts', + courseInfo: 'Infos sur le cours', + expertBackground: 'Antécédents de l\'expert', + profCertification: 'Certification professionnelle', + practiceHours: 'heures de pratique', + supervisionCount: 'Supervision par an', + outOf: 'sur', + schedule: 'Programme', + signUp: 'Inscrivez-vous maintenant', + noData: 'Aucune donnée', + notFound: 'Non trouvé', + errors: { + invalidEmail: 'L\'adresse e-mail n\'est pas valide', + emptyEmail: 'Veuillez saisir votre e-mail', + emptyPass: 'Veuillez saisir votre mot de passe', + confirmPass: 'Veuillez confirmer votre mot de passe', + notMatchPass: 'Les nouveaux mots de passe que vous avez saisis ne sont pas identiques', + emptyCancelReason: 'Veuillez saisir la raison', + approvingSession: 'Erreur lors de l\'approbation de la session', + finishingSession: 'Erreur lors de la fin de la session', + emptyComment: 'Veuillez saisir votre commentaire', + }, } diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 9b7e3f1..575b165 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Inizia a crescere con BB', 'bb-expert': 'Diventa esperto BB', + home: 'Home', blog: 'Blog&Notizie' }, registration: 'Registrazione', enter: 'Inserisci', + enterAccount: 'Inserisci account', account: 'Il mio account', logout: 'Disconnetti', + decline: 'Rifiuta', + send: 'Invia', deleteAcc: 'Elimina account', footer: { faq: 'Domande frequenti', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Prossime sessioni', requested: 'Sessioni richieste', - recent: 'Sessioni recenti' + recent: 'Sessioni recenti', + cancelReason: 'Inserisci un motivo per l\'annullamento della sessione', + reasonPlaceholder: 'Descrivi il motivo del rifiuto', + decline: 'Rifiuta sessione', + confirm: 'Conferma sessione', + join: 'Partecipa alla sessione', + start: 'Avvia sessione', + finish: 'Termina sessione', + comments: 'Commenti', + myComments: 'I miei commenti', + addComment: 'Aggiungi nuovo commento', + commentPlaceholder: 'Il tuo commento', }, + room: { + upcoming: 'Prossime sale', + requested: 'Sale richieste', + recent: 'Sale recenti', + newRoom: 'Nuova sala' + }, + agreementText: 'Ho letto e accetto i termini dell\'Accordo con l\'utente,', + userAgreement: '', + privacyPolicy: 'Informativa sulla privacy', + readMore: 'Leggi di più', photoDesc: 'Aggiungi una foto vera: il volto di una persona è sempre più credibile.', dayStart: 'Inizio del giorno', topic: 'Argomento', @@ -37,6 +62,12 @@ export default { oldPass: 'Vecchia password', newPass: 'Nuova password', confirmPass: 'Conferma password', + forgotPass: 'Hai dimenticato la password', + resetPassText: 'Un link per reimpostare la password è stato inviato al tuo indirizzo e-mail', + or: 'o', + facebook: 'Account Facebook', + apple: 'Account Apple', + google: 'Account Google', 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', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'Per prezzo decrescente', details: 'Dettagli', sessionLang: 'Lingua sessione', + direction: 'Direzione', fromTo: 'da $ a $', apply: 'Applica', save: 'Salva', changePass: 'Cambia password', + resetPass: 'Reimposta password', getStarted: 'Inizia', delete: 'Elimina', - today: 'Oggi' + today: 'Oggi', + back: 'Indietro', + backToExperts: 'Torna all\'elenco degli esperti', + courseInfo: 'Informazioni sul corso', + expertBackground: 'Background esperto', + profCertification: 'Certificazione professionale', + practiceHours: 'ore di pratica', + supervisionCount: 'supervisioni per anno', + outOf: 'su', + schedule: 'Programma', + signUp: 'Iscriviti ora', + noData: 'Nessun dato', + notFound: 'Non trovato', + errors: { + invalidEmail: 'L\'indirizzo e-mail non è valido', + emptyEmail: 'Inserisci l\'e-mail', + emptyPass: 'Inserisci la password', + confirmPass: 'Conferma la password', + notMatchPass: 'Le nuove password inserite non corrispondono', + emptyCancelReason: 'Inserisci il motivo', + approvingSession: 'Errore nell\'approvazione della sessione', + finishingSession: 'Errore durante la chiusura della sessione', + emptyComment: 'Inserisci il tuo commento', + }, } diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index 480915f..16da8a3 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -11,12 +11,16 @@ export default { menu: { 'bb-client': 'Начните свой рост с BB', 'bb-expert': 'Станьте экспертом BB', + home: 'Главная', blog: 'Блог и новости' }, registration: 'Регистрация', enter: 'Войти', + enterAccount: 'Войти в аккаунт', account: 'Учетная запись', logout: 'Выйти', + decline: 'Отклонить', + send: 'Отправить', deleteAcc: 'Удалить учетную запись', footer: { faq: 'Частые вопросы', @@ -25,8 +29,29 @@ export default { session: { upcoming: 'Предстоящие сессии', requested: 'Запрошенные сессии', - recent: 'Недавние сессии' + recent: 'Недавние сессии', + cancelReason: 'Введите причину отмены сессии', + reasonPlaceholder: 'Опишите причину отказа', + decline: 'Отклонить сессию', + confirm: 'Подтвердить сессию', + join: 'Присоединиться к сессии', + start: 'Начать сессию', + finish: 'Завершить сессию', + comments: 'Комментарии', + myComments: 'Мои комментарии', + addComment: 'Добавить новый', + commentPlaceholder: 'Ваш комментарий', }, + room: { + upcoming: 'Предстоящие комнаты', + requested: 'Запрошенные комнаты', + recent: 'Недавние комнаты', + newRoom: 'Новая комната' + }, + agreementText: 'Я прочитал и согласен с условиями Пользовательского соглашения,', + userAgreement: 'Пользовательского соглашения', + privacyPolicy: 'Политикой конфиденциальности', + readMore: 'Читать дальше', photoDesc: 'Добавьте реальную фотографию, ведь лицо человека всегда вызывает больше доверия.', dayStart: 'День начала', topic: 'Тема', @@ -37,6 +62,12 @@ export default { oldPass: 'Старый пароль', newPass: 'Новый пароль', confirmPass: 'Подтвердите пароль', + forgotPass: 'Забыли пароль', + resetPassText: 'Ссылка для сброса пароля была отправлена на ваш E-mail', + or: 'или', + facebook: 'Аккаунт Facebook', + apple: 'Аккаунт Apple', + google: 'Аккаунт Google', becomeExpert: '', insertInfo: 'Введите личные данные и начните свой путь эксперта BBuddy', changeUserData: 'Добавить и изменить информацию о себе можно в любое время', @@ -49,11 +80,36 @@ export default { sortPriceDesc: 'Цена по убыванию', details: 'Информация', sessionLang: 'Язык сессии', + direction: 'Направление', fromTo: 'от $ до $', apply: 'Применить', save: 'Сохранить', changePass: 'Изменить пароль', + resetPass: 'Сбросить пароль', getStarted: 'Начать работу', delete: 'Удалить', - today: 'Сегодня' + today: 'Сегодня', + back: 'Назад', + backToExperts: 'Вернуться к списку экспертов', + courseInfo: 'Информация о курсе', + expertBackground: 'Профессиональный опыт эксперта', + profCertification: 'Профессиональная сертификация', + practiceHours: 'часов практики', + supervisionCount: 'часов супервизии в год', + outOf: 'из', + schedule: 'Расписание', + signUp: 'Записаться сейчас', + noData: 'Нет данных', + notFound: 'Не найдено', + errors: { + invalidEmail: 'Адрес электронной почты недействителен', + emptyEmail: 'Пожалуйста, введите ваш E-mail', + emptyPass: 'Пожалуйста, введите ваш пароль', + confirmPass: 'Пожалуйста, подтвердите ваш пароль', + notMatchPass: 'Введенные новые пароли не совпадают', + emptyCancelReason: 'Пожалуйста, введите причину', + approvingSession: 'Ошибка при подтверждении сессии', + finishingSession: 'Ошибка при завершении сессии', + emptyComment: 'Пожалуйста, введите ваш комментарий', + }, } diff --git a/src/styles/_default.scss b/src/styles/_default.scss index 1cde301..3f7f347 100644 --- a/src/styles/_default.scss +++ b/src/styles/_default.scss @@ -70,11 +70,11 @@ a { line-height: normal; @media (min-width: 576px) { - @include rem(30); + @include rem(28); } @media (min-width: 768px) { - @include rem(42); + @include rem(40); } } @@ -309,6 +309,14 @@ a { margin-bottom: 10px; } + .title-h4 { + color: #003B46; + font-size: 1rem; + line-height: 1.5rem; + font-weight: bold; + margin-bottom: 10px; + } + .title-h3 { color: #003B46; font-size: 18px; @@ -561,6 +569,10 @@ a { font-style: normal; font-weight: 400; line-height: 160%; + + &__auto { + width: auto; + } } .btn-video { @@ -762,6 +774,8 @@ a { padding: 16px; flex-flow: wrap; display: flex; + margin-bottom: 42px; + top: 24px; &__avatar { width: 80px; @@ -815,8 +829,8 @@ a { color: $white; @include rem(13); font-style: normal; - font-weight: 500; - line-height: 123.077%; + font-weight: 300; + line-height: 120%; display: flex; flex-flow: column; gap: 8px; @@ -831,6 +845,7 @@ a { display: flex; gap: 8px; flex-flow: wrap; + align-items: center; & > span { display: block; @@ -853,7 +868,7 @@ a { } @media (min-width: 992px) { - padding: 38px 24px 38px 24px; + padding: 24px; flex-flow: nowrap; justify-content: space-between; @@ -861,7 +876,7 @@ a { width: 220px; height: 220px; position: absolute; - top: 24px; + top: -18px; left: 24px; } @@ -903,7 +918,7 @@ a { } &__info { - @include rem(18); + @include rem(15); flex-flow: nowrap; gap: 16px; i { @@ -922,6 +937,17 @@ a { } } +.expert-info { + display: flex; + gap: 16px; + align-items: center; + padding-top: 24px; + + .skills__list { + align-self: initial; + } +} + .base-text { color: #66A5AD; @include rem(13); @@ -964,7 +990,7 @@ a { flex-flow: wrap; .breadcrumb-item { - color: #6FB98F; + color: #003B46; @include rem(18); font-style: normal; font-weight: 500; @@ -985,14 +1011,6 @@ a { } } } - - @media (min-width: 768px) { - margin-bottom: 24px; - - .breadcrumb-item { - @include rem(24); - } - } } diff --git a/src/styles/_form.scss b/src/styles/_form.scss index 13da4b1..db377ea 100644 --- a/src/styles/_form.scss +++ b/src/styles/_form.scss @@ -57,6 +57,21 @@ } } +.form-fieldset { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 16px; + + .ant-form-item { + margin: 0 !important; + } +} + +.form-actions { + display: flex; + gap: 16px; +} + input { &.base-input { height: 54px; diff --git a/src/styles/_main.scss b/src/styles/_main.scss index 37e38ba..c5a0c43 100644 --- a/src/styles/_main.scss +++ b/src/styles/_main.scss @@ -6,6 +6,14 @@ max-height: 570px; position: relative; + .b-main-desc { + min-height: 440px; + display: flex; + flex-direction: column; + align-items: flex-start; + justify-content: center; + } + @media (min-width: 576px) { max-height: 600px; } @@ -110,16 +118,12 @@ text-align: center; margin-bottom: 16px; padding-top: 8px; - - @media (min-width: 768px) { - padding-top: 24px; - } + position: relative; + z-index: 1; @media (min-width: 992px) { max-width: 690px; text-align: left; - padding-top: 70px; - margin-bottom: 24px; } } @@ -148,11 +152,11 @@ line-height: 133.333%; @media (min-width: 576px) { - @include rem(16); + @include rem(15); } @media (min-width: 768px) { - @include rem(24); + @include rem(18); } @media (max-width: 1249px) { @@ -166,13 +170,14 @@ display: flex; align-items: center; gap: 16px; - margin-bottom: 24px; justify-content: center; position: relative; + margin: auto; @media (min-width: 992px) { gap: 24px; justify-content: flex-start; + margin: 0; } @media (min-width: 1200px) { @@ -321,16 +326,17 @@ .main-articles { position: relative; background: #DFF5FB; - padding-bottom: 4px; + padding-bottom: 10px; + margin-bottom: 94px; &:before { content: ""; position: absolute; background-image: url(/images/wave-top.svg); background-position: center; - height: 86px; + height: 80px; width: 100%; - top: -86px; + top: -80px; overflow: hidden; background-size: 893px 87px; diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index d3ffa48..200aea8 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -7,12 +7,8 @@ margin-bottom: 16px; } - .expert-card { - } - .title-h2 { margin-bottom: 16px; - padding-top: 16px; } .title-h3 { @@ -29,6 +25,9 @@ flex-flow: column; gap: 8px; align-items: center; + padding-bottom: 16px; + border-bottom: 1px solid #C4DFE6; + margin-bottom: 24px; &__text { color: #6FB98F; @@ -80,14 +79,6 @@ margin-bottom: 24px; } - .title-h2 { - padding-top: 24px; - } - - .expert-card { - margin-bottom: 32px; - } - .offers-list { margin-bottom: 72px; } @@ -95,7 +86,6 @@ } .b-news { - &__header { position: relative; padding-top: 24px; @@ -749,6 +739,11 @@ &__item { border-bottom: 1px solid #C4DFE6; width: 100%; + padding: 8px 0; + + &:first-child { + padding: 0 0 8px; + } &:last-child { border-bottom: none; @@ -783,13 +778,11 @@ display: flex; align-items: center; justify-content: space-between; - height: 49px; gap: 8px; - color: #003B46 !important; - @include rem(18); + @include rem(15); font-style: normal; font-weight: 600; - line-height: 133.333%; + line-height: 160%; text-decoration: none; padding-right: 32px; position: relative; @@ -809,7 +802,7 @@ &.active { - color: #66A5AD !important; + color: #003B46 !important; padding-right: 0; &:before { @@ -831,16 +824,16 @@ &__item { cursor: pointer; - height: 48px; - padding-bottom: 16px; + height: 40px; + padding: 8px 0; position: relative; display: flex; gap: 10px; justify-content: center; color: #66A5AD; - @include rem(24); + @include rem(18); font-style: normal; - font-weight: 300; + font-weight: 500; line-height: 133.333%; align-items: center; width: calc(33.33333333% - 8px); @@ -865,12 +858,12 @@ position: absolute; bottom: 0; width: 100%; - height: 8px; + height: 4px; border-radius: 8px; } &.active { - color: #6FB98F; + color: #2C7873; &:before{ background: #2C7873; } @@ -1113,6 +1106,31 @@ } } +.b-work { + display: flex; + gap: 16px; + align-items: center; + + &__text { + color: #2C7873; + @include rem(16); + font-style: normal; + font-weight: 600; + line-height: 100%; + } + + &__description { + display: flex; + flex-direction: column; + gap: 16px; + align-items: flex-start; + } + + .btn-apply { + width: auto; + } +} + .image-info { display: flex; justify-content: center; diff --git a/src/styles/view/_buttons.scss b/src/styles/view/_buttons.scss index 8f69701..dfd9b8a 100644 --- a/src/styles/view/_buttons.scss +++ b/src/styles/view/_buttons.scss @@ -6,6 +6,17 @@ height: 54px !important; box-shadow: 0px 2px 4px 0px rgba(102, 165, 173, 0.32) !important; + &_yellow { + background: #FFBD00 !important; + border-color: #FFBD00 !important; + color: #003B46 !important; + font-size: 15px !important; + border-radius: 8px !important; + height: 54px !important; + box-shadow: none !important; + padding: 4px 24px !important; + } + &.danger { background: #D93E5C !important; box-shadow: none !important; @@ -30,6 +41,11 @@ border-radius: 8px !important; height: 54px !important; + &.danger { + border-color: #D93E5C !important; + color: #D93E5C !important; + } + span { margin-inline-end: 0 !important; line-height: 15px !important; @@ -38,12 +54,12 @@ &__logout { width: 100%; - height: 49px !important; + height: 24px !important; color: #D93E5C !important; font-style: normal; font-weight: 600 !important; padding: 0 !important; - font-size: 1.125rem !important; + font-size: 15px !important; justify-content: flex-start !important; } } diff --git a/src/styles/view/_slider.scss b/src/styles/view/_slider.scss index b3de62c..9456dfa 100644 --- a/src/styles/view/_slider.scss +++ b/src/styles/view/_slider.scss @@ -32,7 +32,8 @@ &:focus, &:hover { &::after { - box-shadow: 0 0 0 12px rgba(102, 165, 173, .2) !important; + outline: none !important; + box-shadow: 0 0 0 8px rgba(102, 165, 173, .2) !important; } } } From ff74e5ba49db5916e8aba83a012e94447e15ed75 Mon Sep 17 00:00:00 2001 From: SD Date: Fri, 9 Aug 2024 12:54:33 +0400 Subject: [PATCH 09/23] feat: add experts profile --- messages/de.json | 2 +- messages/es.json | 2 +- messages/fr.json | 2 +- messages/it.json | 2 +- src/actions/helpers.ts | 39 +-- src/actions/hooks/useProfileSettings.ts | 17 +- src/actions/profile.ts | 94 +++++++- src/actions/upload.ts | 13 + .../add-offer/page.tsx | 4 +- .../new-topic/page.tsx | 2 +- .../account/(account)/expert-profile/page.tsx | 66 +++++ .../account/(account)/settings/page.tsx | 8 - .../(account)/work-with-us/coaching/page.tsx | 133 ---------- .../account/(account)/work-with-us/page.tsx | 25 -- src/app/loading.tsx | 2 +- src/components/Account/ProfileSettings.tsx | 227 +++++++++++------- .../sessions/SessionDetailsContent.tsx | 2 +- .../ExpertProfile/EmptyExpertProfile.tsx | 19 ++ .../ExpertProfile/ExpertProfile.tsx | 98 ++++++++ src/components/ExpertProfile/MyOffers.tsx | 34 +++ .../ExpertProfile/content/ExpertEducation.tsx | 65 +++++ .../ExpertProfile/content/ExpertPayData.tsx | 28 +++ .../ExpertProfile/content/ExpertSchedule.tsx | 28 +++ .../ExpertProfile/content/ExpertTags.tsx | 47 ++++ src/components/ExpertProfile/index.ts | 5 + src/components/Experts/ExpertDetails.tsx | 2 +- src/components/Modals/EditExpertTagsModal.tsx | 114 +++++++++ src/i18nKeys/de.ts | 16 +- src/i18nKeys/en.ts | 15 +- src/i18nKeys/es.ts | 20 +- src/i18nKeys/fr.ts | 18 +- src/i18nKeys/it.ts | 20 +- src/i18nKeys/ru.ts | 20 +- src/lib/apiClient.ts | 5 +- src/styles/_default.scss | 15 ++ src/styles/_modal.scss | 49 ++++ src/styles/_pages.scss | 67 +++++- src/types/education.ts | 44 ++++ src/types/experts.ts | 46 +--- src/types/file.ts | 12 + src/types/practice.ts | 29 +++ src/types/profile.ts | 40 ++- src/types/schedule.ts | 10 + src/types/tags.ts | 15 ++ src/utils/account.ts | 18 +- 45 files changed, 1175 insertions(+), 364 deletions(-) create mode 100644 src/actions/upload.ts rename src/app/[locale]/account/(account)/{work-with-us/coaching => expert-profile}/add-offer/page.tsx (97%) rename src/app/[locale]/account/(account)/{work-with-us => expert-profile}/new-topic/page.tsx (97%) create mode 100644 src/app/[locale]/account/(account)/expert-profile/page.tsx delete mode 100644 src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx delete mode 100644 src/app/[locale]/account/(account)/work-with-us/page.tsx create mode 100644 src/components/ExpertProfile/EmptyExpertProfile.tsx create mode 100644 src/components/ExpertProfile/ExpertProfile.tsx create mode 100644 src/components/ExpertProfile/MyOffers.tsx create mode 100644 src/components/ExpertProfile/content/ExpertEducation.tsx create mode 100644 src/components/ExpertProfile/content/ExpertPayData.tsx create mode 100644 src/components/ExpertProfile/content/ExpertSchedule.tsx create mode 100644 src/components/ExpertProfile/content/ExpertTags.tsx create mode 100644 src/components/ExpertProfile/index.ts create mode 100644 src/components/Modals/EditExpertTagsModal.tsx create mode 100644 src/types/education.ts create mode 100644 src/types/file.ts create mode 100644 src/types/practice.ts create mode 100644 src/types/schedule.ts diff --git a/messages/de.json b/messages/de.json index 14d749a..27ce487 100644 --- a/messages/de.json +++ b/messages/de.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Einen Experten finden", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/es.json b/messages/es.json index 0342887..3f7470b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Encontrar un experto", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/fr.json b/messages/fr.json index 48b42e3..bad196d 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Trouver un expert", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/messages/it.json b/messages/it.json index 379adca..0874d7a 100644 --- a/messages/it.json +++ b/messages/it.json @@ -79,7 +79,7 @@ } }, "Experts": { - "title": "Find an expert", + "title": "Trova un esperto", "filter": { "price": "Price from {from}€ to {to}€", "duration": "Duration from {from}min to {to}min", diff --git a/src/actions/helpers.ts b/src/actions/helpers.ts index d3841c5..6522b69 100644 --- a/src/actions/helpers.ts +++ b/src/actions/helpers.ts @@ -2,7 +2,7 @@ import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'; import { apiClient } from '../lib/apiClient'; type RequiredConfigParams = Required> & Pick, 'data'>; -export type PageRequestConfig = RequiredConfigParams & { locale?: string, token?: string }; +export type PageRequestConfig = RequiredConfigParams & Partial> & { locale?: string, token?: string }; export const apiRequest = async ( baseParams: PageRequestConfig, @@ -15,29 +15,30 @@ export const apiRequest = async ( headers: { 'X-User-Language': baseParams?.locale || 'en', 'X-Referrer-Channel': 'site', - ...(baseParams?.token ? { Authorization: `Bearer ${baseParams.token}` } : {}) + ...(baseParams?.token ? { Authorization: `Bearer ${baseParams.token}` } : {}), + ...(baseParams.headers || {}) } }; const response: AxiosResponse = await apiClient.request, T>(config as AxiosRequestConfig); return response.data; } catch (err) { - const { - response: { - status: responseCode = null, - statusText = '', - data: { message = '', status: errorKey = '' } = {}, - } = {}, - code: statusCode = '', - } = err as AxiosError; - - throw new Error( - JSON.stringify({ - statusCode, - statusMessage: message || statusText, - responseCode, - errorKey, - }), - ); + // const { + // response: { + // status: responseCode = null, + // statusText = '', + // data: { message = '', status: errorKey = '' } = {}, + // } = {}, + // code: statusCode = '', + // } = err as AxiosError; + // + // throw new Error( + // JSON.stringify({ + // statusCode, + // statusMessage: message || statusText, + // responseCode, + // errorKey, + // }), + // ); } }; diff --git a/src/actions/hooks/useProfileSettings.ts b/src/actions/hooks/useProfileSettings.ts index 58b02e8..bff3878 100644 --- a/src/actions/hooks/useProfileSettings.ts +++ b/src/actions/hooks/useProfileSettings.ts @@ -1,18 +1,17 @@ 'use client' import { useCallback, useEffect, useState } from 'react'; -import { Profile } from '../../types/profile'; -import { getPersonalData } from '../profile'; +import { ProfileData, ProfileRequest } from '../../types/profile'; +import { getPersonalData, setPersonData } from '../profile'; import { useLocalStorage } from '../../hooks/useLocalStorage'; import { AUTH_TOKEN_KEY } from '../../constants/common'; export const useProfileSettings = (locale: string) => { const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); - const [profileSettings, setProfileSettings] = useState(); + const [profileSettings, setProfileSettings] = useState(); const [fetchLoading, setFetchLoading] = useState(false); - const [saveLoading, setSaveLoading] = useState(false); - useEffect(() => { + const fetchProfileSettings = () => { if (jwt) { getPersonalData(locale, jwt) .then((data) => { @@ -25,16 +24,14 @@ export const useProfileSettings = (locale: string) => { setFetchLoading(false); }); } - }, []); + }; - const save = useCallback(() => { - - }, []); + const save = useCallback((data: ProfileRequest) => setPersonData(data, locale, jwt), []); return { fetchLoading, + fetchProfileSettings, save, - saveLoading, profileSettings }; }; diff --git a/src/actions/profile.ts b/src/actions/profile.ts index d2c9d96..5d517ef 100644 --- a/src/actions/profile.ts +++ b/src/actions/profile.ts @@ -1,7 +1,25 @@ -import { Profile } from '../types/profile'; +import { PayInfo, Profile, ProfileRequest, ProfileData } from '../types/profile'; +import { ExpertsTags } from '../types/tags'; +import { EducationData, EducationDTO } from '../types/education'; +import { PracticeData, PracticeDTO } from '../types/practice'; +import { ScheduleDTO } from '../types/schedule'; import { apiRequest } from './helpers'; -export const setPersonData = (data: { login: string, password: string, role: string, languagesLinks: any[] }, locale: string, token: string): Promise<{ userData: Profile }> => apiRequest({ +export const getUserData = (locale: string, token: string): Promise => apiRequest({ + url: '/home/userdata', + method: 'post', + locale, + token +}); + +export const getPersonalData = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person1', + method: 'post', + locale, + token +}); + +export const setPersonData = (data: ProfileRequest, locale: string, token: string): Promise<{ userData: Profile }> => apiRequest({ url: '/home/applyperson1', method: 'post', data, @@ -9,9 +27,77 @@ export const setPersonData = (data: { login: string, password: string, role: str token }); -export const getPersonalData = (locale: string, token: string): Promise => apiRequest({ - url: '/home/userdata', +export const getEducation = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person2', method: 'post', locale, token }); + +export const setEducation = (locale: string, token: string, data: EducationData): Promise => apiRequest({ + url: '/home/applyperson2', + method: 'post', + data, + locale, + token +}); + +export const getTags = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person3', + method: 'post', + locale, + token +}); + +export const setTags = (locale: string, token: string, data: ExpertsTags): Promise => apiRequest({ + url: '/home/applyperson3', + method: 'post', + data, + locale, + token +}); + +export const getPractice = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person4', + method: 'post', + locale, + token +}); + +export const setPractice = (locale: string, token: string, data: PracticeData): Promise => apiRequest({ + url: '/home/applyperson4', + method: 'post', + data, + locale, + token +}); + +export const getSchedule = (locale: string, token: string): Promise => apiRequest({ + url: '/home/person51', + method: 'post', + locale, + token +}); + +export const setSchedule = (locale: string, token: string, data: ScheduleDTO): Promise => apiRequest({ + url: '/home/applyperson51', + method: 'post', + data, + locale, + token +}); + +export const getPayData = (locale: string, token: string): Promise<{ person6Data?: PayInfo }> => apiRequest({ + url: '/home/person6', + method: 'post', + locale, + token +}); + +export const setPayData = (locale: string, token: string, data: PayInfo): Promise => apiRequest({ + url: '/home/applyperson6', + method: 'post', + data, + locale, + token +}); diff --git a/src/actions/upload.ts b/src/actions/upload.ts new file mode 100644 index 0000000..f41ef95 --- /dev/null +++ b/src/actions/upload.ts @@ -0,0 +1,13 @@ +import { ExpertDocument } from '../types/file'; +import { apiRequest } from './helpers'; + +export const setUploadFile = (locale: string, token: string, data: any): Promise => apiRequest({ + url: '/home/uploadfile', + method: 'post', + data, + locale, + token, + headers: { + 'Content-Type': 'multipart/form-data' + } +}); diff --git a/src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx similarity index 97% rename from src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx rename to src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx index 04ec673..e669b63 100644 --- a/src/app/[locale]/account/(account)/work-with-us/coaching/add-offer/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx @@ -7,12 +7,12 @@ export default function AddOffer() { <>
      1. - + Work With Us
      2. - + Coaching
      3. diff --git a/src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx b/src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx similarity index 97% rename from src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx rename to src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx index a7b33ed..f8b6774 100644 --- a/src/app/[locale]/account/(account)/work-with-us/new-topic/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/new-topic/page.tsx @@ -7,7 +7,7 @@ export default function NewTopic() { <>
        1. - + Work With Us
        2. diff --git a/src/app/[locale]/account/(account)/expert-profile/page.tsx b/src/app/[locale]/account/(account)/expert-profile/page.tsx new file mode 100644 index 0000000..c9cd00d --- /dev/null +++ b/src/app/[locale]/account/(account)/expert-profile/page.tsx @@ -0,0 +1,66 @@ +'use client' + +import { useEffect, useState } from 'react'; +import { message } from 'antd'; +// import { unstable_setRequestLocale } from 'next-intl/server'; +import { ExpertData } from '../../../../../types/profile'; +import { AUTH_TOKEN_KEY } from '../../../../../constants/common'; +import { useLocalStorage } from '../../../../../hooks/useLocalStorage'; +import { getEducation, getPersonalData, getTags, getPractice, getSchedule, getPayData } from '../../../../../actions/profile'; +import { ExpertProfile } from '../../../../../components/ExpertProfile'; +import { Loader } from '../../../../../components/view/Loader'; + +export default function ExpertProfilePage({ params: { locale } }: { params: { locale: string } }) { + // unstable_setRequestLocale(locale); + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [data, setData] = useState(); + + useEffect(() => { + if (jwt) { + setLoading(true); + Promise.all([ + getPersonalData(locale, jwt), + getEducation(locale, jwt), + getTags(locale, jwt), + getPractice(locale, jwt), + getSchedule(locale, jwt), + getPayData(locale, jwt) + ]) + .then(([person, education, tags, practice, schedule, payData]) => { + console.log('person', person); + console.log('education', education); + console.log('tags', tags); + console.log('practice', practice); + console.log('schedule', schedule); + console.log('payData', payData); + setData({ + person, + education, + tags, + practice, + schedule, + payData + }); + }) + .catch(() => { + message.error('Не удалось загрузить данные эксперта'); + }) + .finally(() => { + setLoading(false); + }) + } + }, [jwt]); + + return ( + + {data && ( + + )} + + ); +}; diff --git a/src/app/[locale]/account/(account)/settings/page.tsx b/src/app/[locale]/account/(account)/settings/page.tsx index 87504f3..984d596 100644 --- a/src/app/[locale]/account/(account)/settings/page.tsx +++ b/src/app/[locale]/account/(account)/settings/page.tsx @@ -1,18 +1,10 @@ import React, { Suspense } from 'react'; -import type { Metadata } from 'next'; import { unstable_setRequestLocale } from 'next-intl/server'; -import { useTranslations } from 'next-intl'; import { ProfileSettings } from '../../../../../components/Account'; import { i18nText } from '../../../../../i18nKeys'; -export const metadata: Metadata = { - title: 'Bbuddy - Account - Profile Settings', - description: 'Bbuddy desc Profile settings' -}; - export default function Settings({ params: { locale } }: { params: { locale: string } }) { unstable_setRequestLocale(locale); - const t = useTranslations('Account.Settings'); return ( <> diff --git a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx b/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx deleted file mode 100644 index 2b00727..0000000 --- a/src/app/[locale]/account/(account)/work-with-us/coaching/page.tsx +++ /dev/null @@ -1,133 +0,0 @@ -import React from 'react'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import { Link } from '../../../../../../navigation'; -import { i18nText } from '../../../../../../i18nKeys'; - -export default function Coaching({ params: { locale } }: { params: { locale: string } }) { - unstable_setRequestLocale(locale); - - return ( - <> -
            -
          1. - - Work With Us - -
          2. -
          3. Coaching
          4. -
          -
          -
          -
          -
          - -
          -
          -
          - David -
          -
          - 12 Practice hours -
          -
          - 15 Supervision per year -
          -
          -
          -
          -
          - Edit - Add Offer -
          -
          -
          -

          - My Offers -

          -
          -
          -
          -
          - Senior Software Engineer -
          -
          - Edit - Remove -
          -
          -
          - 45$ / 45min -
          -
          -
          Engineering & Data
          -
          Engineering & Data
          -
          +6
          -
          -
          - I have worked across a variety of organizations, lead teams, and delivered quality software - for 8 years. In that time I've worked as an independent consultant, at agencies as a team - lead, and as a senior engineer at Auth0. I also host a podcast - https://anchor.fm/work-in-programming where I break down how … -
          -
          -
          -
          -
          -

          - About Coach -

          -
          - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
          -
          - -
          -

          - Education -

          -
          -

          Psychologist

          -
          - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
          -
          - -
          -
          -
          -
          -

          {i18nText('profCertification', locale)}

          -
          -
          - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
          -
          -
          -
          -

          - Trainings | Seminars | Courses -

          -
          -
          - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
          -
          -
          -
          -

          - MBA Information -

          -
          -
          - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
          -
          -
          - - ); -} diff --git a/src/app/[locale]/account/(account)/work-with-us/page.tsx b/src/app/[locale]/account/(account)/work-with-us/page.tsx deleted file mode 100644 index 465b5a0..0000000 --- a/src/app/[locale]/account/(account)/work-with-us/page.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; -import { unstable_setRequestLocale } from 'next-intl/server'; -import { i18nText } from '../../../../../i18nKeys'; - -export default function WorkWithUs({ params: { locale } }: { params: { locale: string } }) { - unstable_setRequestLocale(locale); - - return ( - <> -
            -
          1. {i18nText('accountMenu.work-with-us', locale)}
          2. -
          -
          -
          - -
          -
          -
          {i18nText('insertInfo', locale)}
          -
          {i18nText('changeUserData', locale)}
          - -
          -
          - - ); -} diff --git a/src/app/loading.tsx b/src/app/loading.tsx index b64006d..937170a 100644 --- a/src/app/loading.tsx +++ b/src/app/loading.tsx @@ -6,4 +6,4 @@ export default function Loading() { ...loading
    ); -} +}; diff --git a/src/components/Account/ProfileSettings.tsx b/src/components/Account/ProfileSettings.tsx index 0d6c4a4..674ae2c 100644 --- a/src/components/Account/ProfileSettings.tsx +++ b/src/components/Account/ProfileSettings.tsx @@ -1,125 +1,190 @@ 'use client'; import React, { FC, useEffect, useState } from 'react'; -import { Form, Upload } from 'antd'; -import type { UploadFile, UploadProps } from 'antd'; +import { Button, Form, message, Upload } from 'antd'; +import type { GetProp, UploadFile, UploadProps } from 'antd'; import ImgCrop from 'antd-img-crop'; import { CameraOutlined, DeleteOutlined } from '@ant-design/icons'; import { useRouter } from '../../navigation'; import { i18nText } from '../../i18nKeys'; -import { Profile } from '../../types/profile'; +import { ProfileRequest } from '../../types/profile'; +import { validateImage } from '../../utils/account'; import { useProfileSettings } from '../../actions/hooks/useProfileSettings'; import { CustomInput } from '../view/CustomInput'; import { OutlinedButton } from '../view/OutlinedButton'; import { FilledYellowButton } from '../view/FilledButton'; -import { DeleteAccountModal } from "../Modals/DeleteAccountModal"; +import { DeleteAccountModal } from '../Modals/DeleteAccountModal'; +import { Loader } from '../view/Loader'; type ProfileSettingsProps = { locale: string; }; -// type FileType = Parameters>[0]; +type FileType = Parameters>[0]; export const ProfileSettings: FC = ({ locale }) => { - const [form] = Form.useForm(); - const { profileSettings } = useProfileSettings(locale); + const [form] = Form.useForm(); + const { profileSettings, fetchProfileSettings, save, fetchLoading } = useProfileSettings(locale); const [showDeleteModal, setShowDeleteModal] = useState(false); + const [saveLoading, setSaveLoading] = useState(false); + const [photo, setPhoto] = useState(); const router = useRouter(); + useEffect(() => { + fetchProfileSettings() + }, []); + useEffect(() => { if (profileSettings) { form.setFieldsValue(profileSettings); } }, [profileSettings]); - const saveProfileSettings = () => { + const onSaveProfile = () => { form.validateFields() - .then(() => { - console.log('success') + .then(({ login, surname, username }) => { + const { phone, role, languagesLinks } = profileSettings; + const newProfile: ProfileRequest = { + phone, + role, + login, + surname, + username, + isPasswordKeepExisting: true, + isFaceImageKeepExisting: true, + languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || [] + }; + + // if (photo) { + // console.log(photo); + // const formData = new FormData(); + // formData.append('file', photo as FileType); + // + // newProfile.faceImage = photo; + // newProfile.isFaceImageKeepExisting = false; + // } + + console.log(newProfile); + + setSaveLoading(true); + save(newProfile) + .then(() => { + fetchProfileSettings(); + }) + .catch(() => { + message.error('Не удалось сохранить изменения'); + }) + .finally(() => { + setSaveLoading(false); + }) }) } - const [fileList, setFileList] = useState(); + const beforeCrop = (file: UploadFile) => { + return validateImage(file, true); + } - const onChange: UploadProps['onChange'] = ({ fileList: newFileList }) => { - setFileList(newFileList); - }; + const beforeUpload = (file: UploadFile) => { + const isValid = validateImage(file); - const onPreview = async (file: UploadFile) => { - // let src = file.url as string; - // if (!src) { - // src = await new Promise((resolve) => { - // const reader = new FileReader(); - // reader.readAsDataURL(file.originFileObj as FileType); - // reader.onload = () => resolve(reader.result as string); - // }); - // } - // const image = new Image(); - // image.src = src; - // const imgWindow = window.open(src); - // imgWindow?.document.write(image.outerHTML); - }; + if (isValid) { + setPhoto(file); + } + + return false; + } const onDeleteAccount = () => setShowDeleteModal(true); return ( -
    -
    -
    - -
    - {/* - - - - */} -
    -
    - - + + {photo && } + + + +
    +
    + + + +
    +
    + + + +
    + {/*
    + + +
    */} +
    + + + +
    -
    - - - +
    + + {i18nText('save', locale)} + + router.push('change-password')}> + {i18nText('changePass', locale)} + + } + danger + > + {i18nText('deleteAcc', locale)} +
    - {/*
    - - - -
    */} -
    - - - -
    -
    -
    - {i18nText('save', locale)} - router.push('change-password')}> - {i18nText('changePass', locale)} - - } - danger - > - {i18nText('deleteAcc', locale)} - -
    - setShowDeleteModal(false)} - /> - + setShowDeleteModal(false)} + /> + + ); }; diff --git a/src/components/Account/sessions/SessionDetailsContent.tsx b/src/components/Account/sessions/SessionDetailsContent.tsx index f714917..f32622e 100644 --- a/src/components/Account/sessions/SessionDetailsContent.tsx +++ b/src/components/Account/sessions/SessionDetailsContent.tsx @@ -283,7 +283,7 @@ export const SessionDetailsContent = ({ session, locale, activeType, startSessio ))} {(isCoach ? session?.clientComments : session?.coachComments)?.length > 0 && (
    - {isCoach ? 'Client Comments' : 'Coach Comments'} + {isCoach ? i18nText('session.clientComments', locale) : i18nText('session.coachComments', locale)}
    )} {(isCoach ? session?.clientComments : session?.coachComments)?.map(({ id , comment }) => ( diff --git a/src/components/ExpertProfile/EmptyExpertProfile.tsx b/src/components/ExpertProfile/EmptyExpertProfile.tsx new file mode 100644 index 0000000..aaa7fcf --- /dev/null +++ b/src/components/ExpertProfile/EmptyExpertProfile.tsx @@ -0,0 +1,19 @@ +import { i18nText } from '../../i18nKeys'; + +export const EmptyExpertProfile = ({ locale }: { locale: string }) => ( + <> +
      +
    1. {i18nText('accountMenu.expert-profile', locale)}
    2. +
    +
    +
    + +
    +
    +
    {i18nText('insertInfo', locale)}
    +
    {i18nText('changeUserData', locale)}
    + +
    +
    + +); diff --git a/src/components/ExpertProfile/ExpertProfile.tsx b/src/components/ExpertProfile/ExpertProfile.tsx new file mode 100644 index 0000000..669bf77 --- /dev/null +++ b/src/components/ExpertProfile/ExpertProfile.tsx @@ -0,0 +1,98 @@ +'use client' + +import { useState } from 'react'; +import { message } from 'antd'; +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { ExpertData } from '../../types/profile'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { getTags } from '../../actions/profile'; +import { Loader } from '../view/Loader'; +import { LinkButton } from '../view/LinkButton'; +import { ExpertTags } from './content/ExpertTags'; +import { ExpertSchedule } from './content/ExpertSchedule'; +import { ExpertPayData } from './content/ExpertPayData'; +import { ExpertEducation } from './content/ExpertEducation'; + +type ExpertProfileProps = { + locale: string; + data: ExpertData; + updateData: (data: ExpertData) => void; +}; + +export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState<(keyof ExpertData)[]>([]); + + const updateExpert = (key: keyof ExpertData) => { + switch (key) { + case 'tags': + setLoading([key]); + getTags(locale, jwt) + .then((tags) => { + updateData({ + ...data, + tags + }); + }) + .catch(() => message.error('Не удалось обновить направления')) + .finally(() => setLoading([])); + break; + default: + break; + } + }; + + return ( + <> +
      +
    1. {i18nText('coaching', locale)}
    2. +
    +
    +
    +
    + +
    +
    +
    + David +
    +
    +
    +
    +
    +
    +

    {i18nText('aboutCoach', locale)}

    +

    person1 + person4

    + } + /> +
    +
    + {`12 ${i18nText('practiceHours', locale)}`} +
    +
    + {`15 ${i18nText('supervisionCount', locale)}`} +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
    +
    +
    + + + + + + +
    + + ) +}; diff --git a/src/components/ExpertProfile/MyOffers.tsx b/src/components/ExpertProfile/MyOffers.tsx new file mode 100644 index 0000000..7a3111c --- /dev/null +++ b/src/components/ExpertProfile/MyOffers.tsx @@ -0,0 +1,34 @@ +export const MyOffers = () => ( +
    +

    + My Offers +

    +
    +
    +
    +
    + Senior Software Engineer +
    +
    + Edit + Remove +
    +
    +
    + 45$ / 45min +
    +
    +
    Engineering & Data
    +
    Engineering & Data
    +
    +6
    +
    +
    + I have worked across a variety of organizations, lead teams, and delivered quality software + for 8 years. In that time I've worked as an independent consultant, at agencies as a team + lead, and as a senior engineer at Auth0. I also host a podcast + https://anchor.fm/work-in-programming where I break down how … +
    +
    +
    +
    +); diff --git a/src/components/ExpertProfile/content/ExpertEducation.tsx b/src/components/ExpertProfile/content/ExpertEducation.tsx new file mode 100644 index 0000000..8ddc1e6 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertEducation.tsx @@ -0,0 +1,65 @@ +import { EditOutlined } from '@ant-design/icons'; +import { EducationDTO } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertEducationProps = { + locale: string; + data?: EducationDTO; +}; + +export const ExpertEducation = ({ locale, data }: ExpertEducationProps) => { + return ( +
    +
    +
    +

    {i18nText('education', locale)}

    +

    person2

    + } + /> +
    +
    +

    Psychologist

    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
    +
    + +
    +
    +
    +
    +

    {i18nText('profCertification', locale)}

    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
    +
    +
    +
    +

    + {`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`} +

    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
    +
    +
    +
    +

    {i18nText('mba', locale)}

    +
    +
    + Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra + malesuada, ligula sem tempor risus, non posuere urna diam a libero. +
    +
    +
    +
    + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertPayData.tsx b/src/components/ExpertProfile/content/ExpertPayData.tsx new file mode 100644 index 0000000..648aed2 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertPayData.tsx @@ -0,0 +1,28 @@ +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../../i18nKeys'; +import { PayInfo } from '../../../types/profile'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertPayDataProps = { + locale: string; + data?: PayInfo +}; + +export const ExpertPayData = ({ locale, data }: ExpertPayDataProps) => { + return ( +
    +
    +
    +

    Card data - person6

    + } + /> +
    +
    + Card +
    +
    +
    + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertSchedule.tsx b/src/components/ExpertProfile/content/ExpertSchedule.tsx new file mode 100644 index 0000000..907aa22 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertSchedule.tsx @@ -0,0 +1,28 @@ +import { EditOutlined } from '@ant-design/icons'; +import { ScheduleDTO } from '../../../types/schedule'; +import { i18nText } from '../../../i18nKeys'; +import { LinkButton } from '../../view/LinkButton'; + +type ExpertScheduleProps = { + locale: string; + data?: ScheduleDTO; +}; + +export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => { + return ( +
    +
    +
    +

    Schedule - person51

    + } + /> +
    +
    + Schedule +
    +
    +
    + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertTags.tsx b/src/components/ExpertProfile/content/ExpertTags.tsx new file mode 100644 index 0000000..7441bed --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertTags.tsx @@ -0,0 +1,47 @@ +'use client' + +import { useState } from 'react'; +import { Tag } from 'antd'; +import { EditOutlined } from '@ant-design/icons'; +import { i18nText } from '../../../i18nKeys'; +import { ExpertsTags } from '../../../types/tags'; +import { ExpertData } from '../../../types/profile'; +import { LinkButton } from '../../view/LinkButton'; +import { EditExpertTagsModal } from '../../Modals/EditExpertTagsModal'; + +type ExpertTagsProps = { + locale: string; + data?: ExpertsTags; + updateExpert: (key: keyof ExpertData) => void; +} + +export const ExpertTags = ({ locale, data, updateExpert }: ExpertTagsProps) => { + const [showEdit, setShowEdit] = useState(false); + + return ( +
    +
    +
    +

    {i18nText('direction', locale)}

    + } + onClick={() => setShowEdit(true)} + /> +
    +
    + {data?.themesTags && data.themesTags?.length > 0 && data.themesTags + .filter(({ isActive, isSelected }) => isActive && isSelected) + .map(({ id, name }) => {name})} +
    +
    + setShowEdit(false)} + refresh={() => updateExpert('tags')} + /> +
    + ); +}; diff --git a/src/components/ExpertProfile/index.ts b/src/components/ExpertProfile/index.ts new file mode 100644 index 0000000..0fc5003 --- /dev/null +++ b/src/components/ExpertProfile/index.ts @@ -0,0 +1,5 @@ +'use client' + +export * from './EmptyExpertProfile'; +export * from './ExpertProfile'; +export * from './MyOffers'; diff --git a/src/components/Experts/ExpertDetails.tsx b/src/components/Experts/ExpertDetails.tsx index 4398016..e2c942c 100644 --- a/src/components/Experts/ExpertDetails.tsx +++ b/src/components/Experts/ExpertDetails.tsx @@ -98,7 +98,7 @@ export const ExpertPractice: FC = ({ expert, locale }) => { return practiceCases?.length > 0 ? (
    -

    Successful Cases From Practice

    +

    {i18nText('successfulCase', locale)}

    {practiceCases?.map(({ id, description, themesGroupIds }) => { const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id)); diff --git a/src/components/Modals/EditExpertTagsModal.tsx b/src/components/Modals/EditExpertTagsModal.tsx new file mode 100644 index 0000000..9b1677a --- /dev/null +++ b/src/components/Modals/EditExpertTagsModal.tsx @@ -0,0 +1,114 @@ +'use client'; + +import React, {FC, useCallback, useEffect, useState} from 'react'; +import { Modal, Button, List, message } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { ExpertsTags, Tag } from '../../types/tags'; +import { CustomSwitch } from '../view/CustomSwitch'; +import { setTags } from '../../actions/profile'; + +type EditExpertTagsModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: ExpertsTags; + refresh: () => void; +}; + +export const EditExpertTagsModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh +}) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [expertTags, setExpertTags] = useState([]); + + useEffect(() => { + setExpertTags(data?.themesTags || []); + }, [data]); + + const onSaveTags = () => { + setLoading(true); + setTags(locale, jwt, { themesGroups: data?.themesGroups, themesTags: expertTags }) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить направления'); + }) + .finally(() => { + setLoading(false); + }) + }; + + const updateTag = useCallback((id: number, isSelected: boolean) => { + setExpertTags(expertTags.map((tag) => { + if (tag.id === id) { + tag.isSelected = isSelected; + } + return tag; + })) + }, [expertTags, setExpertTags]); + + return ( + } + > +
    +
    {i18nText('direction', locale)}
    +
    + {data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => ( +
    +

    {name}

    + {expertTags?.length > 0 ? ( +
    + (isActive && groupId == id)) || []} + split={false} + style={{ width: '100%' }} + renderItem={({ id, name, isSelected }) => ( + +
    +
    {name}
    + updateTag(id, checked)} + /> +
    +
    + )} + /> +
    + ) :
    No tags
    } +
    + ))} +
    +
    + +
    +
    +
    + ); +}; diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index 21bb176..f27812b 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -4,9 +4,9 @@ export default { notifications: 'Benachrichtigung', support: 'Hilfe & Support', information: 'Rechtliche Informationen', - settings: 'Profileinstellungen', + settings: 'Kontoeinstellungen', messages: 'Nachrichten', - 'work-with-us': 'Arbeite mit uns' + 'expert-profile': 'Expertenprofil' }, menu: { 'bb-client': 'Mit BB wachsen', @@ -41,6 +41,8 @@ export default { myComments: 'Meine Kommentare', addComment: 'Neuen Kommentar hinzufügen', commentPlaceholder: 'Ihr Kommentar', + clientComments: 'Kundenkommentare', + coachComments: 'Trainerkommentare' }, room: { upcoming: 'Zukünftige Räume', @@ -84,6 +86,7 @@ export default { fromTo: 'von $ bis $', apply: 'Anwenden', save: 'Speichern', + edit: 'Bearbeiten', changePass: 'Passwort ändern', resetPass: 'Passwort zurücksetzen', getStarted: 'Loslegen', @@ -98,9 +101,18 @@ export default { supervisionCount: 'Supervision pro Jahr', outOf: 'von', schedule: 'Zeitplan', + successfulCase: 'Erfolgreiche Fälle aus der Praxis', signUp: 'Jetzt anmelden', noData: 'Keine Daten', notFound: 'Nicht gefunden', + trainings: 'Trainings', + seminars: 'Seminare', + courses: 'Kurse', + mba: 'MBA-Information', + aboutCoach: 'Über Coach', + education: 'Bildung', + coaching: 'Coaching', + errors: { invalidEmail: 'Die E-Mail-Adresse ist ungültig', emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index cc538c3..9857844 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notification', support: 'Help & Support', information: 'Legal Information', - settings: 'Profile Settings', + settings: 'Account Settings', messages: 'Messages', - 'work-with-us': 'Work With Us' + 'expert-profile': 'Expert profile' }, menu: { 'bb-client': 'Start grow with BB', @@ -41,6 +41,8 @@ export default { myComments: 'My comments', addComment: 'Add new', commentPlaceholder: 'Your comment', + clientComments: 'Client Comments', + coachComments: 'Coach Comments' }, room: { upcoming: 'Upcoming Rooms', @@ -84,6 +86,7 @@ export default { fromTo: 'from $ to $', apply: 'Apply', save: 'Save', + edit: 'Edit', changePass: 'Change password', resetPass: 'Reset password', getStarted: 'Get started', @@ -98,9 +101,17 @@ export default { supervisionCount: 'Supervision per year', outOf: 'out of', schedule: 'Schedule', + successfulCase: 'Successful Cases From Practice', signUp: 'Sign up now', noData: 'No data', notFound: 'Not found', + trainings: 'Trainings', + seminars: 'Seminars', + courses: 'Courses', + mba: 'MBA Information', + aboutCoach: 'About Coach', + education: 'Education', + coaching: 'Coaching', errors: { invalidEmail: 'The email address is not valid', emptyEmail: 'Please enter your E-mail', diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index 9c7d096..f94d8a5 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notificación', support: 'Ayuda y asistencia', information: 'Información jurídica', - settings: 'Ajustes del perfil', + settings: 'Ajustes de cuenta', messages: 'Mensajes', - 'work-with-us': 'Trabaja con nosotros' + 'expert-profile': 'Perfil del experto' }, menu: { 'bb-client': 'Empieza a crecer con BB', @@ -41,6 +41,8 @@ export default { myComments: 'Mis comentarios', addComment: 'Añadir nuevo comentario', commentPlaceholder: 'Tu comentario', + clientComments: 'Comentarios del cliente', + coachComments: 'Comentarios del entrenador' }, room: { upcoming: 'Próximas salas', @@ -84,6 +86,7 @@ export default { fromTo: 'de $ a $', apply: 'Solicitar', save: 'Guardar', + edit: 'Editar', changePass: 'Cambiar contraseña', resetPass: 'Restablecer contraseña', getStarted: 'Empieza', @@ -94,13 +97,22 @@ export default { courseInfo: 'Información del curso', expertBackground: 'Antecedentes del experto', profCertification: 'Certificación profesional', - practiceHours: 'horas de práctica', - supervisionCount: 'supervisiones anuales', + practiceHours: 'Horas de práctica', + supervisionCount: 'Supervisiones anuales', outOf: 'de', schedule: 'Horario', + successfulCase: 'Casos de éxito de la práctica', signUp: 'Regístrate ahora', noData: 'Sin datos', notFound: 'No encontrado', + trainings: 'Formación', + seminars: 'Seminarios', + courses: 'Cursos', + mba: 'Información sobre máster en ADE (MBA)', + aboutCoach: 'Sobre el coach', + education: 'Educación', + coaching: 'Coaching', + errors: { invalidEmail: 'La dirección de correo electrónico no es válida', emptyEmail: 'Introduce tu correo electrónico', diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index 9b5c471..7978155 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notification', support: 'Aide et support', information: 'Informations légales', - settings: 'Paramètres du profil', + settings: 'Paramètres du compte', messages: 'Messages', - 'work-with-us': 'Travaillez avec nous' + 'expert-profile': 'Profil de l\'expert' }, menu: { 'bb-client': 'Commencez à vous développer avec BB', @@ -41,6 +41,8 @@ export default { myComments: 'Mes commentaires', addComment: 'Ajouter un nouveau commentaire', commentPlaceholder: 'Votre commentaire', + clientComments: 'Commentaires du client', + coachComments: 'Commentaires du coach' }, room: { upcoming: 'Salles futures', @@ -84,6 +86,7 @@ export default { fromTo: 'de $ à $', apply: 'Appliquer', save: 'Sauvegarder', + edit: 'Modifier', changePass: 'Modifier le mot de passe', resetPass: 'Réinitialiser le mot de passe', getStarted: 'Commencer', @@ -94,13 +97,22 @@ export default { courseInfo: 'Infos sur le cours', expertBackground: 'Antécédents de l\'expert', profCertification: 'Certification professionnelle', - practiceHours: 'heures de pratique', + practiceHours: 'Heures de pratique', supervisionCount: 'Supervision par an', outOf: 'sur', schedule: 'Programme', + successfulCase: 'Cas réussis de la pratique', signUp: 'Inscrivez-vous maintenant', noData: 'Aucune donnée', notFound: 'Non trouvé', + trainings: 'Formations', + seminars: 'Séminaires', + courses: 'Cours', + mba: 'Infos Maîtrise en gestion', + aboutCoach: 'À propos du coach', + education: 'Éducation', + coaching: 'Coaching', + errors: { invalidEmail: 'L\'adresse e-mail n\'est pas valide', emptyEmail: 'Veuillez saisir votre e-mail', diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 575b165..5520c2a 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -4,9 +4,9 @@ export default { notifications: 'Notifica', support: 'Assistenza e supporto', information: 'Informazioni legali', - settings: 'Impostazioni profilo', + settings: 'Impostazioni account', messages: 'Messaggi', - 'work-with-us': 'Lavora con noi' + 'expert-profile': 'Profilo dell\'esperto' }, menu: { 'bb-client': 'Inizia a crescere con BB', @@ -41,6 +41,8 @@ export default { myComments: 'I miei commenti', addComment: 'Aggiungi nuovo commento', commentPlaceholder: 'Il tuo commento', + clientComments: 'Commenti del cliente', + coachComments: 'Commenti dell\'allenatore' }, room: { upcoming: 'Prossime sale', @@ -84,6 +86,7 @@ export default { fromTo: 'da $ a $', apply: 'Applica', save: 'Salva', + edit: 'Modifica', changePass: 'Cambia password', resetPass: 'Reimposta password', getStarted: 'Inizia', @@ -94,13 +97,22 @@ export default { courseInfo: 'Informazioni sul corso', expertBackground: 'Background esperto', profCertification: 'Certificazione professionale', - practiceHours: 'ore di pratica', - supervisionCount: 'supervisioni per anno', + practiceHours: 'Ore di pratica', + supervisionCount: 'Supervisioni per anno', outOf: 'su', schedule: 'Programma', + successfulCase: 'Casi di successo dalla pratica', signUp: 'Iscriviti ora', noData: 'Nessun dato', notFound: 'Non trovato', + trainings: 'Training', + seminars: 'Seminari', + courses: 'Corsi', + mba: 'Info sull\'MBA', + aboutCoach: 'Informazioni sul coach', + education: 'Istruzione', + coaching: 'Coaching', + errors: { invalidEmail: 'L\'indirizzo e-mail non è valido', emptyEmail: 'Inserisci l\'e-mail', diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index 16da8a3..aa99952 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -4,9 +4,9 @@ export default { notifications: 'Уведомления', support: 'Служба поддержки', information: 'Юридическая информация', - settings: 'Настройки профиля', + settings: 'Настройки учетной записи', messages: 'Сообщения', - 'work-with-us': 'Сотрудничество' + 'expert-profile': 'Профиль эксперта' }, menu: { 'bb-client': 'Начните свой рост с BB', @@ -41,6 +41,8 @@ export default { myComments: 'Мои комментарии', addComment: 'Добавить новый', commentPlaceholder: 'Ваш комментарий', + clientComments: 'Комментарии клиента', + coachComments: 'Комментарии коуча' }, room: { upcoming: 'Предстоящие комнаты', @@ -84,6 +86,7 @@ export default { fromTo: 'от $ до $', apply: 'Применить', save: 'Сохранить', + edit: 'Редактировать', changePass: 'Изменить пароль', resetPass: 'Сбросить пароль', getStarted: 'Начать работу', @@ -94,13 +97,22 @@ export default { courseInfo: 'Информация о курсе', expertBackground: 'Профессиональный опыт эксперта', profCertification: 'Профессиональная сертификация', - practiceHours: 'часов практики', - supervisionCount: 'часов супервизии в год', + practiceHours: 'Часов практики', + supervisionCount: 'Часов супервизии в год', outOf: 'из', schedule: 'Расписание', + successfulCase: 'Успешные случаи из практики', signUp: 'Записаться сейчас', noData: 'Нет данных', notFound: 'Не найдено', + trainings: 'Тренинги', + seminars: 'Семинары', + courses: 'Курсы', + mba: 'Информация о MBA', + aboutCoach: 'О коуче', + education: 'Образование', + coaching: 'Коучинг', + errors: { invalidEmail: 'Адрес электронной почты недействителен', emptyEmail: 'Пожалуйста, введите ваш E-mail', diff --git a/src/lib/apiClient.ts b/src/lib/apiClient.ts index f6beada..713dbe7 100644 --- a/src/lib/apiClient.ts +++ b/src/lib/apiClient.ts @@ -12,7 +12,10 @@ export const onSuccessRequestCallback = (config: InternalAxiosRequestConfig) => // if (IS_DEV && !newConfig.headers.Authorization && getAuthToken()) { // newConfig.headers.Authorization = `Bearer ${getAuthToken()}`; // } - newConfig.headers['Content-Type'] = 'application/json'; + + if (!newConfig.headers['Content-Type']) { + newConfig.headers['Content-Type'] = 'application/json'; + } return newConfig; }; diff --git a/src/styles/_default.scss b/src/styles/_default.scss index 3f7f347..93f42f9 100644 --- a/src/styles/_default.scss +++ b/src/styles/_default.scss @@ -8,6 +8,21 @@ body{ --font: var(--font-comfortaa), -apple-system, BlinkMacSystemFont, "Segoe UI", "Helvetica Neue", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol"; font-family: var(--font); background-color: #ffffff; + + & * { + &::-webkit-scrollbar { + width: 8px; + height: 8px; + } + &::-webkit-scrollbar-track { + background: #C4DFE6; + border-radius: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: #2C7873; + border-radius: 8px; + } + } } *::selection { diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index c04824a..bf5423e 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -32,6 +32,55 @@ padding: 44px 40px; gap: 24px; } + + &__expert { + &__title { + color: #003B46; + @include rem(20); + font-style: normal; + font-weight: 600; + line-height: 133.333%; + } + + &__button { + width: 100%; + + button { + width: 100% !important; + } + } + + &__inner { + height: 60vh; + overflow-y: auto; + + & > div { + display: flex; + flex-direction: column; + padding-right: 20px; + + & > * { + width: 100%; + } + } + } + + &__content { + display: flex; + flex-direction: column; + padding: 40px; + gap: 24px; + + .title-h4 { + color: #003B46; + @include rem(16); + font-style: normal; + font-weight: 700; + line-height: 150%; + padding-bottom: 16px; + } + } + } } .ant-modal-mask { diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index 200aea8..49ceb51 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -1140,15 +1140,13 @@ height: 146px; object-fit: cover; } - } .coaching-info { display: flex; flex-flow: column; justify-content: space-between; - gap: 24px; - margin-bottom: 24px; + gap: 16px; .card-profile { border: none !important; @@ -1172,9 +1170,6 @@ } @media (min-width: 768px) { - flex-flow: nowrap; - gap: 10px; - &__wrap-btn { display: flex; gap: 10px; @@ -1183,8 +1178,66 @@ } } +.coaching-profile { + display: flex; + gap: 16px; + align-items: flex-start; + + &__portrait { + width: 86px; + height: 86px; + border-radius: 16px; + border: 2px solid #FFF; + background: lightgray 50%; + box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32); + overflow: hidden; + + img { + object-fit: cover; + width: 100%; + height: 100%; + display: block; + border-radius: 16px; + } + } + + &__inner { + padding-top: 10px; + } + + &__name { + color: #003B46; + @include rem(18); + font-weight: 600; + line-height: 150%; + } +} + .coaching-section { - margin-bottom: 24px; + &__wrap { + border-top: 1px solid #C4DFE6; + padding-top: 16px; + display: flex; + flex-direction: column; + gap: 16px; + } + + &__title { + display: flex; + width: 100%; + justify-content: space-between; + align-self: center; + + .b-button__link { + height: 24px !important; + } + } + + .title-h2 { + color: #003B46; + @include rem(18); + line-height: 24px; + } .base-text { margin-bottom: 0; diff --git a/src/types/education.ts b/src/types/education.ts new file mode 100644 index 0000000..aa41a70 --- /dev/null +++ b/src/types/education.ts @@ -0,0 +1,44 @@ +import { ExpertDocument } from './file'; + +export type Details = { + id: number; + userId?:number; + title?: string; + description?: string; + document?: ExpertDocument; +}; + +export type Certificate = { + id: number; + userId?: number; + associationLevelId?: number; + document?: ExpertDocument; +}; + +export type Experience = { + id: number, + userId?: number, + title?: string, + description?: string +}; + +export type Association = { + id: number; + name?: string; +}; + +export type AssociationLevel = Association & { associationId?: number }; + +export type EducationData = { + certificates?: Certificate[], + educations?: Details[], + trainings?: Details[], + mbas?: Details[], + experiences?: Experience[] +}; + +export interface EducationDTO { + person2Data?: EducationData, + associations?: Association[], + associationLevels?: AssociationLevel[] +} diff --git a/src/types/experts.ts b/src/types/experts.ts index 160db87..5917d31 100644 --- a/src/types/experts.ts +++ b/src/types/experts.ts @@ -1,4 +1,5 @@ import { Tag, ThemeGroups } from './tags'; +import { Association, AssociationLevel, EducationData } from './education'; export type GeneralFilter = Filter & AdditionalFilter; @@ -19,34 +20,6 @@ export type AdditionalFilter = { coachSort?: string; }; -export type File = { - id: number; - fileType: string; - url: string; -}; - -export interface ExpertDocument { - fileName: string; - original?: File; - preview?: File; - fullSize?: File; -} - -export type Details = { - id: number; - userId?:number; - title?: string; - description?: string; - document?: ExpertDocument; -}; - -export type Certificate = { - id: number; - userId?: number; - associationLevelId?: number; - document?: ExpertDocument; -}; - export type Practice = { id: number; userId?: number; @@ -61,16 +34,6 @@ export type ThemeGroup = { canDeleted?: boolean; }; -export type Association = { - id: number; - name: string; -}; - -export type AssociationLevel = { - id: number; - associationId: number; - name: string; -}; export interface ExpertItem { id: number; @@ -98,14 +61,9 @@ export type ExpertsData = { }; export type ExpertDetails = { - publicCoachDetails: ExpertItem & { + publicCoachDetails: ExpertItem & EducationData & { practiceHours?: number; supervisionPerYearId?: number; - educations?: Details[]; - certificates?: Certificate[]; - trainings?: Details[]; - mbas?: Details[]; - experiences?: Details[]; practiceCases?: Practice[]; themesGroups?: ThemeGroup[]; }; diff --git a/src/types/file.ts b/src/types/file.ts new file mode 100644 index 0000000..48a73fd --- /dev/null +++ b/src/types/file.ts @@ -0,0 +1,12 @@ +export type File = { + id: number; + fileType: string; + url: string; +}; + +export interface ExpertDocument { + fileName: string; + original?: File; + preview?: File; + fullSize?: File; +} diff --git a/src/types/practice.ts b/src/types/practice.ts new file mode 100644 index 0000000..438e125 --- /dev/null +++ b/src/types/practice.ts @@ -0,0 +1,29 @@ +import { ExpertsThemesGroups } from './tags'; + +export type Supervision = { + id: number, + name: string +}; + +export type PracticeCase = { + id: number, + userId?: number, + description?: string, + themesGroupIds?: number[] +}; + +export type PracticeData = { + practiceHours?: number, + supervisionPerYearId?: number, + sessionDuration?: number, + sessionCost?: number, + practiceCases?: PracticeCase[] +} + +export interface PracticeDTO { + person4Data: PracticeData & { + themesGroups?: ExpertsThemesGroups[], + supervisionPerYears?: Supervision[], + sessionCosts?: number[] + } +} diff --git a/src/types/profile.ts b/src/types/profile.ts index c606879..548398c 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -1,5 +1,10 @@ -export type Profile = { - id: number; +import { UploadFile } from 'antd'; +import {EducationDTO} from "./education"; +import {ExpertsTags} from "./tags"; +import {PracticeDTO} from "./practice"; +import {ScheduleDTO} from "./schedule"; + +export type ProfileData = { username?: string; surname?: string; fillProgress?: string; @@ -9,4 +14,35 @@ export type Profile = { hasPassword?: boolean; hasExternalLogin?: boolean; isTestMode?: boolean; + phone?: string; + languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[] +} + +export type Profile = ProfileData & { id: number }; + +export type ProfileRequest = { + login?: string; + password?: string; + isPasswordKeepExisting?: boolean; + languagesLinks?: { languageId: number }[]; + username?: string; + surname?: string; + faceImage?: UploadFile; + isFaceImageKeepExisting?: boolean; + phone?: string; }; + +export type PayInfo = { + beneficiaryName?: string, + iban?: string, + bicOrSwift?: string +}; + +export interface ExpertData { + person?: ProfileData, + education?: EducationDTO, + tags?: ExpertsTags, + practice?: PracticeDTO, + schedule?: ScheduleDTO, + payData?: { person6Data?: PayInfo }, +} diff --git a/src/types/schedule.ts b/src/types/schedule.ts new file mode 100644 index 0000000..fc18ca9 --- /dev/null +++ b/src/types/schedule.ts @@ -0,0 +1,10 @@ +export type WorkingTime = { + startDayOfWeekUtc?: string, + startTimeUtc?: number, + endDayOfWeekUtc?: string, + endTimeUtc?: number +} + +export interface ScheduleDTO { + workingTimes?: WorkingTime[] +} diff --git a/src/types/tags.ts b/src/types/tags.ts index 821ebec..eb1985e 100644 --- a/src/types/tags.ts +++ b/src/types/tags.ts @@ -4,6 +4,9 @@ export type Tag = { name: string couchCount?: number; group?: string | null; + isActive?: boolean, + isSelected?: boolean, + canDeleted?: boolean }; export type ThemeGroups = { @@ -27,3 +30,15 @@ export type Language = { } export type Languages = Language[]; + +export type ExpertsThemesGroups = { + id: number, + name: string, + isActive?: boolean, + canDeleted?: boolean +}; + +export interface ExpertsTags { + themesGroups?: ExpertsThemesGroups[], + themesTags?: Tag[] +} diff --git a/src/utils/account.ts b/src/utils/account.ts index 91725b1..e67e439 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -1,6 +1,8 @@ +import { message } from 'antd'; +import type { UploadFile } from 'antd'; import { i18nText } from '../i18nKeys'; -const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'work-with-us']; +const ROUTES = ['sessions', 'notifications', 'support', 'information', 'settings', 'messages', 'expert-profile']; const COUNTS: Record = { sessions: 12, notifications: 5, @@ -12,3 +14,17 @@ export const getMenuConfig = (locale: string) => ROUTES.map((path) => ({ title: i18nText(`accountMenu.${path}`, locale), count: COUNTS[path] || undefined })); + +export const validateImage = (file: UploadFile, showMessage?: boolean): boolean => { + const isImage = file.type === 'image/jpg' || file.type === 'image/jpeg' || file.type === 'image/png' || file.type === 'image/gif'; + if (!isImage && showMessage) { + message.error('You can only upload JPG/PNG file'); + } + + const isLt5M = file.size / 1024 / 1024 <= 5; + if (!isLt5M && showMessage) { + message.error('Image must smaller than 5MB'); + } + + return isImage && isLt5M; +}; From ed756d0646e74073419fcce9bf769f1a078451a5 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Fri, 16 Aug 2024 14:43:07 +0300 Subject: [PATCH 10/23] blog contentful --- .env | 3 + package.json | 2 + src/app/[locale]/blog/[blogId]/page.tsx | 150 ------------ src/app/[locale]/blog/[slug]/page.tsx | 87 +++++++ .../[locale]/blog/category/[slug]/page.tsx | 101 ++++++++ src/app/[locale]/blog/page.tsx | 228 +++++------------- src/lib/contentful/RichText.tsx | 16 ++ src/lib/contentful/authors.ts | 16 ++ src/lib/contentful/blogPosts.ts | 99 ++++++++ src/lib/contentful/blogPostsCategories.ts | 36 +++ src/lib/contentful/contentImage.ts | 27 +++ src/lib/contentful/contentfulClient.ts | 28 +++ src/types/author.ts | 20 ++ src/types/blogPost.ts | 39 +++ src/types/blogPostCategory.ts | 20 ++ src/types/blogWidgets/widgetMedia.ts | 20 ++ src/types/blogWidgets/widgetParagraph.ts | 20 ++ 17 files changed, 589 insertions(+), 323 deletions(-) delete mode 100644 src/app/[locale]/blog/[blogId]/page.tsx create mode 100644 src/app/[locale]/blog/[slug]/page.tsx create mode 100644 src/app/[locale]/blog/category/[slug]/page.tsx create mode 100644 src/lib/contentful/RichText.tsx create mode 100644 src/lib/contentful/authors.ts create mode 100644 src/lib/contentful/blogPosts.ts create mode 100644 src/lib/contentful/blogPostsCategories.ts create mode 100644 src/lib/contentful/contentImage.ts create mode 100644 src/lib/contentful/contentfulClient.ts create mode 100644 src/types/author.ts create mode 100644 src/types/blogPost.ts create mode 100644 src/types/blogPostCategory.ts create mode 100644 src/types/blogWidgets/widgetMedia.ts create mode 100644 src/types/blogWidgets/widgetParagraph.ts diff --git a/.env b/.env index 91a7cbb..509d982 100644 --- a/.env +++ b/.env @@ -1,3 +1,6 @@ NEXT_PUBLIC_SERVER_BASE_URL=https://api.bbuddy.expert/api NEXT_PUBLIC_AGORA_APPID=ed90c9dc42634e5687d4e2e0766b363f +CONTENTFUL_SPACE_ID = voxpxjq7y7vf +CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc +CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE \ No newline at end of file diff --git a/package.json b/package.json index fdfb4c7..4b6b281 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,13 @@ "@ant-design/cssinjs": "^1.18.1", "@ant-design/icons": "^5.2.6", "@ant-design/nextjs-registry": "^1.0.0", + "@contentful/rich-text-react-renderer": "^15.22.9", "agora-rtc-react": "^2.1.0", "agora-rtc-sdk-ng": "^4.20.2", "antd": "^5.12.1", "antd-img-crop": "^4.21.0", "axios": "^1.6.5", + "contentful": "^10.13.3", "dayjs": "^1.11.10", "lodash": "^4.17.21", "next": "14.0.3", diff --git a/src/app/[locale]/blog/[blogId]/page.tsx b/src/app/[locale]/blog/[blogId]/page.tsx deleted file mode 100644 index 131333a..0000000 --- a/src/app/[locale]/blog/[blogId]/page.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React from 'react'; -import type { Metadata } from 'next'; -import { notFound } from 'next/navigation'; - -export const metadata: Metadata = { - title: 'Bbuddy - Blog item', - description: 'Bbuddy desc blog item' -}; - -export function generateStaticParams() { - return [{ blogId: 'news-1' }, { blogId: 'news-2' }]; -} - -export default function BlogItem({ params }: { params: { blogId: string } }) { - if (!params?.blogId) notFound(); - - return ( -
    -
    -

    6 learnings from Shivpuri to Silicon Valley

    -
    Leadership & Management
    -
    - {`news id ${params.blogId}`}
    - I’m excited to kick off this series of newsletters where I’ll be sharing my experiences, learnings, - and best practices which helped me to grow both in my personal and professional life. My hope is to - give back to the community and help anyone connect directly with me who may have got impacted with - recent layoffs, dealing with immigration challenges. -
    -
    - -
    -
    -
    - -
    -
    Sonali Garg
    -
    February 6th, 2023
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    -

    - This is not about layoffs, it's about living with whatever life throws at you.. -

    -

    - Over the past few months, as the macro-economic events have unfolded, I have heard voices filled - with anxiety, helplessness and general lack of confidence to deal with this ambiguity from my - mentees, colleagues, friends and family. I was laid off from Meta last November and I firmly - believe this is nothing but a bump in the road that might seem like a steep climb in the - short-term. I may not have all the answers but this has inspired me to share my story. If you - are looking for a sob story, you can stop reading now. Ever wondered what it takes for a girl - born into a conservative family in a small sleepy town in India, who lost one of her parents at - age 17, earned her living while pursuing engineering, moved to the UK by herself and ended up - working in big tech in Silicon valley? My goal with this series of posts is to inspire and share - my mental models that helped me throughout my professional and personal life. -

    -

    - After completing my engineering, I started my career at a small software company in Bhopal and - then worked for TCS(Tata Consultancy Services), one of the largest IT-outsourcing companies in - the world for almost 5 years. Over the past 14 years, I have worked for big tech companies like - Meta (Facebook) and Google, wore multiple hats, led strategic programs, scaled multi - billion-dollar businesses, built teams and helped achieve business operational excellence. - Throughout my career, I’ve dealt with several challenges from execution to scale to building a - high performance team. A lot of my early struggles were about how to assimilate in a new - culture, create a network in a new environment, earn trust, create and nurture work - relationships into fruitful friendships and so on. -

    -

    - I was born in a conservative family in a small town called ‘Shivpuri’, also known as ‘Mini - Kashmir’ because of its natural beauty. My father was a civil engineer working on Madikheda Dam - on Sindh river and was a strict disciplinarian. He was gone from dawn to dusk and was always - focused. My mother was a teacher in a school that was about 30 kms from our home. We (me and my - sister) would often be left with neighbors to be taken care of and this led us to become - independent at an early age. Our otherwise slow paced, simple life with only a few families - around in the government quarters that were set up to support construction of the dam was filled - with natural beauty, wildlife and a community of close friends. Our lives were balanced and - while my parents worked hard to provide basic needs, we were satisfied. There were only a few - schools with Hindi being the prevalent language as the medium of teaching. There were no - colleges for advanced studies and most girls did not go to college often married off by their - 18th birthday. Generally speaking, we had a joyous childhood with just the basics. While most - folks we interacted with were not highly educated nor ambitious, earned lower middle class - salaries and lacked exposure to the outside world but there was plenty to learn from them. - People had learnt to stick together in good and bad times. They embodied the old school - qualities of hard work, dedication and commitment. Be willing to give it all- hard work, - dedication and commitment. -

    -

    - In 2003, my father passed away suddenly and we found ourselves in crisis. My mother was a - teacher and she did not have time to deal with her grief. Rather, she was struggling to garner - support to get transferred to a school in Bhopal, capital of Madhya Pradesh to be closer to our - maternal grandparents. As we uprooted ourselves from Shivpuri to Bhopal, one of my father’s - loyal friends came to help load the moving truck. While he had nothing to gain out of us, he - continued to serve us until the last day in Shivpuri. Remember, in crisis your team matters more - than any other time. Advocate for them ruthlessly in good and bad times, they will come through - in crisis. -

    -

    - Eventually we found our footing, my mother’s job was transferred to a local school in Bhopal and - I got admission in a government engineering college. My sister was still attending high school - and both of us were teaching tuition classes to middle school students in the evenings to make - ends meet. I also started a tiffin service for a few out of town students while attending - college to pay for my transportation and cost of supplies. We refused to give up. Persevere when - all else fails. -

    -

    - Our 5 years went by quickly in Bhopal as we worked towards improving our financial situation and - I completed my Bachelors in Computer Science. This was the time I first stepped out to live in a - metropolitan city, Mumbai for my job at TCS. This was a paradigm shift from Bhopal and I was - blown away to meet so many talented folks in Mumbai. In my head, I did not belong in this place. - I had imposter syndrome and felt like an outsider trying to make it in a new city. Most people I - met were fluent in more than 1 language, well-dressed, communicated openly and with confidence, - and presented themselves well. I was always in a dilemma when it came to adopting values. It - took me a while to adjust to it but I was still not confident about my work and communication - while my hard skills that I learnt in engineering were top notch. I kept questioning my - abilities but persisted. This was not the first time I was out of my comfort zone. Persist, when - in discomfort. -

    -

    - I worked with multiple global companies who were clients of TCS and was presented an opportunity - to move to Scotland, UK for an year to work for GE, who was also a client. This was my first - opportunity to explore a different culture, food, music, languages etc. I remember working on my - english when in Mumbai, in preparation for my UK trip. It was really difficult to understand the - accent in the UK, even though language was not a barrier. I still remember certain words would - just not get across no matter how hard some of my colleagues tried and they would end up using - signs to convey. Be prepared, opportunities come to those who are prepared. -

    -

    - In 2013, I came to the US on a dependent visa after marriage and quickly realized the curse of - H4 visa. I paved my path by going back to school at UC Berkeley and then jumped back into - building my career from scratch. While working in the US over the past years, I realized college - degrees with good grades and certifications definitely help you to get your foot in the door but - are not enough to be successful in your career. As I was again starting from scratch in a new - culture, determined to do whatever it takes, having done this a few times before, it doesn’t - scare me as much. Never be afraid to start from zero again! -

    -
    -
    -
    - ); -}; diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx new file mode 100644 index 0000000..d1573dc --- /dev/null +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import type { Metadata } from 'next'; +import { draftMode } from 'next/headers' +import { notFound } from 'next/navigation'; +import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/blogPosts"; +import Util from "node:util"; +import RichText from "../../../../lib/contentful/RichText"; + +export const metadata: Metadata = { + title: 'Bbuddy - Blog item', + description: 'Bbuddy desc blog item' +}; + +interface BlogPostPageParams { + slug: string +} + +interface BlogPostPageProps { + params: BlogPostPageParams +} + +export async function generateStaticParams(): Promise { + const blogPosts = await fetchBlogPosts({ preview: false }) + + return blogPosts.map((post) => ({ slug: post.slug })) +} +function renderWidget (widget: Widget) { + switch (widget.type){ + case 'widgetParagraph': + return ( + <> +

    + {widget.widget.subTitle} +

    + + + ) + case 'widgetMedia': + return ( + + ) + } +} + +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(); + + return ( +
    +
    +

    {item.title}

    +
    {item.category}
    +
    + +
    +
    + +
    +
    +
    + +
    +
    {item.author?.name}
    +
    {item.createdAt}
    +
    +
    +
    +
    + + 165 +
    +
    + + Share +
    +
    +
    +
    + {item.body.map(renderWidget)} +
    +
    +
    + ); +}; diff --git a/src/app/[locale]/blog/category/[slug]/page.tsx b/src/app/[locale]/blog/category/[slug]/page.tsx new file mode 100644 index 0000000..696247c --- /dev/null +++ b/src/app/[locale]/blog/category/[slug]/page.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import type { Metadata } from 'next'; +import { draftMode } from 'next/headers' +import {unstable_setRequestLocale} from "next-intl/server"; +import Link from "next/link"; +import {fetchBlogPosts} from "../../../../../lib/contentful/blogPosts"; +import {fetchBlogPostCategories} from "../../../../../lib/contentful/blogPostsCategories"; + +export const metadata: Metadata = { + title: 'Bbuddy - Blog', + description: 'Bbuddy desc blog' +}; + +interface BlogPostPageParams { + slug: string + locale: string +} +interface BlogPostPageProps { + params: BlogPostPageParams +} + +export default async function Blog({params}: { params: BlogPostPageParams }) { + unstable_setRequestLocale(params.locale); + const data = await fetchBlogPosts({ preview: draftMode().isEnabled, locale: params.locale, category: params.slug }) + const cats = await fetchBlogPostCategories(false) + return ( +
    +
    +
    +

    + Mentorship, Career
    + Development & Coaching. +

    +
    +

    The ins-and-outs of building a career in tech, gaining
    experience

    +

    from a mentor, and getting your feet wet with coaching.

    +
    +
    + +
    +
    +
    +
    +
    +
    + { + cats.map((cat, i)=>( + {cat.title} + )) + } +
    +
    +
    +
    +
    +
    + {data.map((item, i) => ( +
  1. + +
    + {item.listImage?.alt}/ +
    +
    +
    +
    + {item.title} +
    +
    {item.category}
    +
    + {item.excerpt} +
    +
    +
    +
    + +
    +
    {item.author.name}
    +
    {item.createdAt}
    +
    +
    +
    +
    + + 165 +
    +
    + + Share +
    +
    +
    +
    + +
  2. + ))} +
    +
    +
    +
    + ); +} diff --git a/src/app/[locale]/blog/page.tsx b/src/app/[locale]/blog/page.tsx index 20f2989..3ce264d 100644 --- a/src/app/[locale]/blog/page.tsx +++ b/src/app/[locale]/blog/page.tsx @@ -1,210 +1,92 @@ 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 Link from "next/link"; +import {fetchBlogPostCategories} from "../../../lib/contentful/blogPostsCategories"; export const metadata: Metadata = { title: 'Bbuddy - Blog', description: 'Bbuddy desc blog' }; -export default function Blog() { + + +export default async function Blog({ params: { locale } }: { params: { locale: string } }) { + unstable_setRequestLocale(locale); + const data = await fetchBlogPosts(false, locale) + const cats = await fetchBlogPostCategories(false) return (

    - Mentorship, Career
    + Mentorship, Career
    Development & Coaching.

    -

    The ins-and-outs of building a career in tech, gaining
    experience

    +

    The ins-and-outs of building a career in tech, gaining
    experience

    from a mentor, and getting your feet wet with coaching.

    - +
    - -
    - -
    -
    -
    -
    - 6 learnings from Shivpuri to Silicon Valley + {data.map((item, i) => ( +
  3. + +
    + {item.listImage?.alt}/
    -
    Leadership & Management
    -
    - I’m excited to kick off this series of newsletters where I’ll be sharing my - experiences, - learnings, and best practices which helped me to grow both in my personal and - professional life. My hope is to give back to the community and help anyone - connect directly with me who may have got impacted with recent layoffs, - dealing with immigration challenges. -
    -
  4. -
    -
    - -
    -
    Sonali Garg
    -
    February 6th, 2023
    +
    +
    +
    + {item.title} +
    +
    {item.category}
    +
    + {item.excerpt} +
    +
    +
    +
    + +
    +
    {item.author.name}
    +
    {item.createdAt}
    +
    +
    +
    +
    + + 165 +
    +
    + + Share +
    +
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - 6 learnings from Shivpuri to Silicon Valley -
    -
    Leadership & Management
    -
    - I’m excited to kick off this series of newsletters where I’ll be sharing my - experiences, - learnings, and best practices which helped me to grow both in my personal and - professional life. My hope is to give back to the community and help anyone - connect directly with me who may have got impacted with recent layoffs, - dealing with immigration challenges. -
    -
    -
    -
    - -
    -
    Sonali Garg
    -
    February 6th, 2023
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - 6 learnings from Shivpuri to Silicon Valley -
    -
    Leadership & Management
    -
    - I’m excited to kick off this series of newsletters where I’ll be sharing my - experiences, - learnings, and best practices which helped me to grow both in my personal and - professional life. My hope is to give back to the community and help anyone - connect directly with me who may have got impacted with recent layoffs, - dealing with immigration challenges. -
    -
    -
    -
    - -
    -
    Sonali Garg
    -
    February 6th, 2023
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    -
    - -
    - -
    -
    -
    -
    - 6 learnings from Shivpuri to Silicon Valley -
    -
    Leadership & Management
    -
    - I’m excited to kick off this series of newsletters where I’ll be sharing my - experiences, - learnings, and best practices which helped me to grow both in my personal and - professional life. My hope is to give back to the community and help anyone - connect directly with me who may have got impacted with recent layoffs, - dealing with immigration challenges. -
    -
    -
    -
    - -
    -
    Sonali Garg
    -
    February 6th, 2023
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    -
    + + + ))}
    diff --git a/src/lib/contentful/RichText.tsx b/src/lib/contentful/RichText.tsx new file mode 100644 index 0000000..597d70b --- /dev/null +++ b/src/lib/contentful/RichText.tsx @@ -0,0 +1,16 @@ +import { Document as RichTextDocument } from '@contentful/rich-text-types' +import { documentToReactComponents } from '@contentful/rich-text-react-renderer' + +type RichTextProps = { + document: RichTextDocument | null +} + +function RichText({ document }: RichTextProps) { + if (!document) { + return null + } + + return <>{documentToReactComponents(document)} +} + +export default RichText \ No newline at end of file diff --git a/src/lib/contentful/authors.ts b/src/lib/contentful/authors.ts new file mode 100644 index 0000000..57a2f30 --- /dev/null +++ b/src/lib/contentful/authors.ts @@ -0,0 +1,16 @@ +import { parseContentfulContentImage } from './contentImage' +import {Author, AuthorEntry} from "../../types/author"; + + + + +export function parseContentfulAuthor(authorEntry?: AuthorEntry): Author | null { + if (!authorEntry) { + return null + } + + return { + name: authorEntry.fields.name || '', + avatar: parseContentfulContentImage(authorEntry.fields.avatar), + } +} \ No newline at end of file diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts new file mode 100644 index 0000000..6fa939b --- /dev/null +++ b/src/lib/contentful/blogPosts.ts @@ -0,0 +1,99 @@ +import { Entry } from 'contentful' +import contentfulClient from './contentfulClient' +import { parseContentfulContentImage } from './contentImage' +import {BlogPost, BlogPostEntry, BlogPostSkeleton} from "../../types/blogPost"; +import {parseContentfulAuthor} from "./authors"; +import dayjs from "dayjs"; +import {WidgetMedia, WidgetMediaEntry} from "../../types/blogWidgets/widgetMedia"; +import {WidgetParagraph} from "../../types/blogWidgets/widgetParagraph"; +import entry from "next/dist/server/typescript/rules/entry"; +import Util from "node:util"; + +type PostEntry = BlogPostEntry//Entry +type widgetEnum = WidgetParagraph | WidgetMedia +export type Widget = { + widget: widgetEnum + type: string +} +type WidgetEntry = WidgetMediaEntry | WidgetParagraph +function parseWidgets(entries?: Array): Array | null { + if (!entries || !entries.length) { + return null + } + return entries.map((entry: WidgetEntry) => { + const wType = entry.sys.contentType.sys.id + let wObj = {} as widgetEnum + switch (wType){ + case 'widgetParagraph': + wObj = { + subTitle: entry.fields.subTitle, + body: entry.fields.body + } + break + case 'widgetMedia': + wObj = { + decription: entry.fields.decription || '', + file: parseContentfulContentImage(entry.fields.file) + } + break + } + return { + type: wType, + widget: wObj + } + }) +} + +export function parseContentfulBlogPost(entry?: PostEntry): BlogPost | null { + if (!entry) { + return null + } + + return { + title: entry.fields.title || '', + slug: entry.fields.slug, + excerpt: entry.fields.excerpt || '', + listImage: parseContentfulContentImage(entry.fields.listImage), + author: parseContentfulAuthor(entry.fields.author), + createdAt: dayjs(entry.sys.createdAt).format('MMM DD, YYYY'), + category: entry.fields.category.fields.title, + body: parseWidgets(entry.fields.body) || [] + } +} + +interface FetchBlogPostsOptions { + preview: boolean + local?: string + category?: string +} +export async function fetchBlogPosts({ preview, category }: FetchBlogPostsOptions): Promise { + const contentful = contentfulClient({ preview }) + const query = { + content_type: 'blogPost', + select: ['fields.title', 'fields.excerpt', 'fields.author', 'fields.listImage', 'fields.author', 'fields.category', 'sys.createdAt', 'fields.slug'], + order: ['sys.createdAt'], + } + if (category){ + query['fields.category.fields.slug'] = category + query['fields.category.sys.contentType.sys.id']='blogPostCategory' + } + const blogPostsResult = await contentful.getEntries(query) + + return blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) +} + +interface FetchBlogPostOptions { + slug: string + preview: boolean +} +export async function fetchBlogPost({ slug, preview }: FetchBlogPostOptions): Promise { + const contentful = contentfulClient({ preview }) + + const blogPostsResult = await contentful.getEntries({ + content_type: 'blogPost', + 'fields.slug': slug, + include: 2, + }) + + return parseContentfulBlogPost(blogPostsResult.items[0]) +} \ No newline at end of file diff --git a/src/lib/contentful/blogPostsCategories.ts b/src/lib/contentful/blogPostsCategories.ts new file mode 100644 index 0000000..84006a3 --- /dev/null +++ b/src/lib/contentful/blogPostsCategories.ts @@ -0,0 +1,36 @@ +import { Entry } from 'contentful' +import contentfulClient from './contentfulClient' +import { parseContentfulContentImage } from './contentImage' +import {BlogPost, BlogPostEntry, BlogPostSkeleton} from "../../types/blogPost"; +import {parseContentfulAuthor} from "./authors"; +import dayjs from "dayjs"; +import {BlogPostCategory, BlogPostCategoryEntry, BlogPostCategorySkeleton} from "../../types/blogPostCategory"; + +type ListEntry = BlogPostCategoryEntry + + + +export function parseContentfulBlogPostCategory(entry?: ListEntry): BlogPostCategory | null { + if (!entry) { + return null + } + return { + title: entry.fields.title || '', + slug: entry.fields.slug || '' + } +} + +interface FetchListOptions { + preview: boolean + local?: string +} +export async function fetchBlogPostCategories({ preview }: FetchListOptions): Promise { + const contentful = contentfulClient({ preview }) + + const results = await contentful.getEntries({ + content_type: 'blogPostCategory', + order: ['fields.title'], + }) + + return results.items.map((entry) => parseContentfulBlogPostCategory(entry) as BlogPostCategory) +} diff --git a/src/lib/contentful/contentImage.ts b/src/lib/contentful/contentImage.ts new file mode 100644 index 0000000..3048548 --- /dev/null +++ b/src/lib/contentful/contentImage.ts @@ -0,0 +1,27 @@ +import { Asset, AssetLink } from 'contentful' + +export interface ContentImage { + src: string + alt: string + width: number + height: number +} + +export function parseContentfulContentImage( + asset?: Asset | { sys: AssetLink } +): ContentImage | null { + if (!asset) { + return null + } + + if (!('fields' in asset)) { + return null + } + + return { + src: asset.fields.file?.url || '', + alt: asset.fields.description || '', + width: asset.fields.file?.details.image?.width || 0, + height: asset.fields.file?.details.image?.height || 0, + } +} \ No newline at end of file diff --git a/src/lib/contentful/contentfulClient.ts b/src/lib/contentful/contentfulClient.ts new file mode 100644 index 0000000..61fcb2e --- /dev/null +++ b/src/lib/contentful/contentfulClient.ts @@ -0,0 +1,28 @@ +import { createClient } from 'contentful' + +const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_PREVIEW_ACCESS_TOKEN } = process.env + +// This is the standard Contentful client. It fetches +// content that has been published. +const client = createClient({ + space: CONTENTFUL_SPACE_ID!, + accessToken: CONTENTFUL_ACCESS_TOKEN!, +}) + +// This is a Contentful client that's been configured +// to fetch drafts and unpublished content. +const previewClient = createClient({ + space: CONTENTFUL_SPACE_ID!, + accessToken: CONTENTFUL_PREVIEW_ACCESS_TOKEN!, + host: 'preview.contentful.com', +}) + +// This little helper will let us switch between the two +// clients easily: +export default function contentfulClient({ preview = false }) { + if (preview) { + return previewClient + } + + return client +} \ No newline at end of file diff --git a/src/types/author.ts b/src/types/author.ts new file mode 100644 index 0000000..05c7eac --- /dev/null +++ b/src/types/author.ts @@ -0,0 +1,20 @@ +import type { ChainModifiers, Entry, EntryFieldTypes, EntrySkeletonType, LocaleCode } from 'contentful' +import {BlogPostFields} from "./blogPost"; +import {ContentImage} from "../lib/contentful/contentImage"; + +export interface AuthorFields { + name: EntryFieldTypes.Symbol + avatar: EntryFieldTypes.AssetLink +} + +export interface Author { + name: string + avatar: ContentImage | null +} + +export type AuthorSkeleton = EntrySkeletonType +export type AuthorEntry = Entry< + AuthorSkeleton, + Modifiers, + Locales +> \ No newline at end of file diff --git a/src/types/blogPost.ts b/src/types/blogPost.ts new file mode 100644 index 0000000..c4776c4 --- /dev/null +++ b/src/types/blogPost.ts @@ -0,0 +1,39 @@ +import type { ChainModifiers, Entry, EntryFieldTypes, EntrySkeletonType, LocaleCode } from 'contentful' +import {Author, AuthorSkeleton} from "./author"; +import {ContentImage} from "../lib/contentful/contentImage"; +import {BlogPostCategorySkeleton} from "./blogPostCategory"; +import {WidgetMedia, WidgetMediaSkeleton} from "./blogWidgets/widgetMedia"; +import {WidgetParagraph, WidgetParagraphSkeleton} from "./blogWidgets/widgetParagraph"; + +export interface BlogPostFields { + title?: EntryFieldTypes.Symbol + slug: EntryFieldTypes.Symbol + excerpt: EntryFieldTypes.Symbol + listImage?: EntryFieldTypes.AssetLink + author?: AuthorSkeleton + category: BlogPostCategorySkeleton + createdAt?: EntryFieldTypes.Date + body?: Array +} + +export interface BlogPostFields extends BlogPostFields{ + body: Array +} + +export interface BlogPost { + title: string + slug: string + excerpt: string + listImage: ContentImage | null + author: Author | null + category: string + createdAt: string + body: Array +} + +export type BlogPostSkeleton = EntrySkeletonType +export type BlogPostEntry = Entry< + BlogPostSkeleton, + Modifiers, + Locales +> \ No newline at end of file diff --git a/src/types/blogPostCategory.ts b/src/types/blogPostCategory.ts new file mode 100644 index 0000000..65567df --- /dev/null +++ b/src/types/blogPostCategory.ts @@ -0,0 +1,20 @@ +import type { ChainModifiers, Entry, EntryFieldTypes, EntrySkeletonType, LocaleCode } from 'contentful' +import {BlogPostFields} from "./blogPost"; +import {ContentImage} from "../lib/contentful/contentImage"; + +export interface BlogPostCategoryFields { + title: EntryFieldTypes.Symbol + slug: EntryFieldTypes.Symbol +} + +export interface BlogPostCategory { + title: string + slug: string +} + +export type BlogPostCategorySkeleton = EntrySkeletonType +export type BlogPostCategoryEntry = Entry< + BlogPostCategorySkeleton, + Modifiers, + Locales +> \ No newline at end of file diff --git a/src/types/blogWidgets/widgetMedia.ts b/src/types/blogWidgets/widgetMedia.ts new file mode 100644 index 0000000..172ae23 --- /dev/null +++ b/src/types/blogWidgets/widgetMedia.ts @@ -0,0 +1,20 @@ +import type { ChainModifiers, Entry, EntryFieldTypes, EntrySkeletonType, LocaleCode } from 'contentful' +import {BlogPostFields} from "./blogPost"; +import {ContentImage} from "../lib/contentful/contentImage"; + +export interface WidgetMediaFields { + decription?: EntryFieldTypes.Symbol + file?: EntryFieldTypes.AssetLink +} + +export interface WidgetMedia { + file: ContentImage | null + decription: string | null +} + +export type WidgetMediaSkeleton = EntrySkeletonType +export type WidgetMediaEntry = Entry< + WidgetMediaSkeleton, + Modifiers, + Locales +> \ No newline at end of file diff --git a/src/types/blogWidgets/widgetParagraph.ts b/src/types/blogWidgets/widgetParagraph.ts new file mode 100644 index 0000000..0d363cd --- /dev/null +++ b/src/types/blogWidgets/widgetParagraph.ts @@ -0,0 +1,20 @@ +import type { ChainModifiers, Entry, EntryFieldTypes, EntrySkeletonType, LocaleCode } from 'contentful' +import {BlogPostFields} from "./blogPost"; +import {ContentImage} from "../lib/contentful/contentImage"; +import { Document as RichTextDocument } from '@contentful/rich-text-types' +export interface WidgetParagraphFields { + subTitle?: EntryFieldTypes.Symbol + body?: EntryFieldTypes.RichText +} + +export interface WidgetParagraph { + subTitle: string + body: RichTextDocument | null +} + +export type WidgetParagraphSkeleton = EntrySkeletonType +export type WidgetParagraphEntry = Entry< + WidgetParagraphSkeleton, + Modifiers, + Locales +> \ No newline at end of file From f92810d320e8b703d7de4279d4c6b914a385286b Mon Sep 17 00:00:00 2001 From: SD Date: Sat, 17 Aug 2024 03:51:27 +0400 Subject: [PATCH 11/23] feat: add expert profile --- .../expert-profile/add-offer/page.tsx | 4 +- .../account/(account)/expert-profile/page.tsx | 20 +- src/app/[locale]/experts/[expertId]/page.tsx | 6 +- src/components/Account/ProfileSettings.tsx | 38 +-- .../ExpertProfile/ExpertProfile.tsx | 156 ++++++++--- .../ExpertProfile/content/ExpertAbout.tsx | 81 ++++++ .../ExpertProfile/content/ExpertEducation.tsx | 157 ++++++++--- .../ExpertProfile/content/ExpertPayData.tsx | 49 +++- .../ExpertProfile/content/ExpertSchedule.tsx | 30 ++- .../ExpertProfile/content/ExpertTags.tsx | 2 +- src/components/Experts/ExpertDetails.tsx | 25 +- src/components/Experts/Filter.tsx | 9 +- .../Modals/EditExpertAboutModal.tsx | 254 ++++++++++++++++++ .../Modals/EditExpertEducationModal.tsx | 152 +++++++++++ .../Modals/EditExpertPayDataModal.tsx | 118 ++++++++ src/components/Modals/EditExpertTagsModal.tsx | 2 +- .../educationModalContent/Certificates.tsx | 205 ++++++++++++++ .../educationModalContent/Educations.tsx | 166 ++++++++++++ .../educationModalContent/Experiences.tsx | 101 +++++++ .../Modals/educationModalContent/Mbas.tsx | 166 ++++++++++++ .../educationModalContent/Trainings.tsx | 166 ++++++++++++ src/components/view/FilledButton.tsx | 6 + src/components/view/LinkButton.tsx | 2 +- src/i18nKeys/de.ts | 34 ++- src/i18nKeys/en.ts | 33 +++ src/i18nKeys/es.ts | 34 ++- src/i18nKeys/fr.ts | 34 ++- src/i18nKeys/it.ts | 34 ++- src/i18nKeys/ru.ts | 36 ++- src/styles/_edu.scss | 35 +++ src/styles/_modal.scss | 13 +- src/styles/_pages.scss | 72 ++++- src/styles/_schedule.scss | 15 ++ src/styles/style.scss | 2 + src/styles/view/_buttons.scss | 17 ++ src/styles/view/_collapse.scss | 15 ++ src/styles/view/_input.scss | 8 + src/styles/view/_practice.scss | 29 ++ src/styles/view/style.scss | 2 + src/types/education.ts | 17 +- src/types/practice.ts | 16 +- src/types/profile.ts | 3 +- src/utils/account.ts | 16 ++ src/utils/time.ts | 62 +++++ 44 files changed, 2281 insertions(+), 161 deletions(-) create mode 100644 src/components/ExpertProfile/content/ExpertAbout.tsx create mode 100644 src/components/Modals/EditExpertAboutModal.tsx create mode 100644 src/components/Modals/EditExpertEducationModal.tsx create mode 100644 src/components/Modals/EditExpertPayDataModal.tsx create mode 100644 src/components/Modals/educationModalContent/Certificates.tsx create mode 100644 src/components/Modals/educationModalContent/Educations.tsx create mode 100644 src/components/Modals/educationModalContent/Experiences.tsx create mode 100644 src/components/Modals/educationModalContent/Mbas.tsx create mode 100644 src/components/Modals/educationModalContent/Trainings.tsx create mode 100644 src/styles/_edu.scss create mode 100644 src/styles/_schedule.scss create mode 100644 src/styles/view/_collapse.scss create mode 100644 src/styles/view/_practice.scss create mode 100644 src/utils/time.ts diff --git a/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx index e669b63..7ed015c 100644 --- a/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Link } from '../../../../../../../navigation'; -import { CustomSelect } from '../../../../../../../components/view/CustomSelect'; +import { Link } from '../../../../../../navigation'; +import { CustomSelect } from '../../../../../../components/view/CustomSelect'; export default function AddOffer() { return ( diff --git a/src/app/[locale]/account/(account)/expert-profile/page.tsx b/src/app/[locale]/account/(account)/expert-profile/page.tsx index c9cd00d..3eaea64 100644 --- a/src/app/[locale]/account/(account)/expert-profile/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/page.tsx @@ -6,7 +6,15 @@ import { message } from 'antd'; import { ExpertData } from '../../../../../types/profile'; import { AUTH_TOKEN_KEY } from '../../../../../constants/common'; import { useLocalStorage } from '../../../../../hooks/useLocalStorage'; -import { getEducation, getPersonalData, getTags, getPractice, getSchedule, getPayData } from '../../../../../actions/profile'; +import { + getEducation, + getPersonalData, + getTags, + getPractice, + getSchedule, + getPayData, + getUserData +} from '../../../../../actions/profile'; import { ExpertProfile } from '../../../../../components/ExpertProfile'; import { Loader } from '../../../../../components/view/Loader'; @@ -15,11 +23,13 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); const [loading, setLoading] = useState(false); const [data, setData] = useState(); + const [isFull, setIsFull] = useState(false); useEffect(() => { if (jwt) { setLoading(true); Promise.all([ + getUserData(locale, jwt), getPersonalData(locale, jwt), getEducation(locale, jwt), getTags(locale, jwt), @@ -27,13 +37,12 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo getSchedule(locale, jwt), getPayData(locale, jwt) ]) - .then(([person, education, tags, practice, schedule, payData]) => { + .then(([profile, person, education, tags, practice, schedule, payData]) => { + console.log('profile', profile); console.log('person', person); console.log('education', education); - console.log('tags', tags); - console.log('practice', practice); console.log('schedule', schedule); - console.log('payData', payData); + setIsFull(profile.fillProgress === 'full'); setData({ person, education, @@ -56,6 +65,7 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo {data && ( + {/*

    All Offers by this Expert

    diff --git a/src/components/Account/ProfileSettings.tsx b/src/components/Account/ProfileSettings.tsx index 674ae2c..21da0a7 100644 --- a/src/components/Account/ProfileSettings.tsx +++ b/src/components/Account/ProfileSettings.tsx @@ -12,7 +12,7 @@ import { validateImage } from '../../utils/account'; import { useProfileSettings } from '../../actions/hooks/useProfileSettings'; import { CustomInput } from '../view/CustomInput'; import { OutlinedButton } from '../view/OutlinedButton'; -import { FilledYellowButton } from '../view/FilledButton'; +import {FilledButton, FilledSquareButton, FilledYellowButton} from '../view/FilledButton'; import { DeleteAccountModal } from '../Modals/DeleteAccountModal'; import { Loader } from '../view/Loader'; @@ -55,14 +55,14 @@ export const ProfileSettings: FC = ({ locale }) => { languagesLinks: languagesLinks?.map(({ languageId }) => ({ languageId })) || [] }; - // if (photo) { - // console.log(photo); - // const formData = new FormData(); - // formData.append('file', photo as FileType); - // - // newProfile.faceImage = photo; - // newProfile.isFaceImageKeepExisting = false; - // } + if (photo) { + console.log(photo); + const formData = new FormData(); + formData.append('file', photo as FileType); + + newProfile.faceImage = `[${(photo as File).arrayBuffer()}]`; + newProfile.isFaceImageKeepExisting = false; + } console.log(newProfile); @@ -99,13 +99,6 @@ export const ProfileSettings: FC = ({ locale }) => { return (
    -
    -
    - -
    -
    {i18nText('photoDesc', locale)}
    -
    = ({ locale }) => { multiple={false} showUploadList={false} > - {photo && } - +
    +
    + } + /> +
    +
    {i18nText('photoDesc', locale)}
    +
    diff --git a/src/components/ExpertProfile/ExpertProfile.tsx b/src/components/ExpertProfile/ExpertProfile.tsx index 669bf77..fa7e13a 100644 --- a/src/components/ExpertProfile/ExpertProfile.tsx +++ b/src/components/ExpertProfile/ExpertProfile.tsx @@ -1,43 +1,96 @@ 'use client' -import { useState } from 'react'; -import { message } from 'antd'; -import { EditOutlined } from '@ant-design/icons'; +import React, { useState } from 'react'; +import {Alert, message} from 'antd'; +import Image from 'next/image'; import { i18nText } from '../../i18nKeys'; -import { ExpertData } from '../../types/profile'; +import { ExpertData, PayInfo, ProfileData } from '../../types/profile'; +import { ExpertsTags } from '../../types/tags'; +import { PracticeDTO } from '../../types/practice'; +import { EducationDTO } from '../../types/education'; +import { ScheduleDTO } from '../../types/schedule'; import { AUTH_TOKEN_KEY } from '../../constants/common'; import { useLocalStorage } from '../../hooks/useLocalStorage'; -import { getTags } from '../../actions/profile'; +import { getTags, getPayData, getEducation, getPractice, getSchedule, getPersonalData } from '../../actions/profile'; import { Loader } from '../view/Loader'; -import { LinkButton } from '../view/LinkButton'; import { ExpertTags } from './content/ExpertTags'; import { ExpertSchedule } from './content/ExpertSchedule'; import { ExpertPayData } from './content/ExpertPayData'; import { ExpertEducation } from './content/ExpertEducation'; +import { ExpertAbout } from './content/ExpertAbout'; type ExpertProfileProps = { locale: string; data: ExpertData; updateData: (data: ExpertData) => void; + isFull: boolean; }; -export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps) => { +type NewDataPartProps = { + key: keyof ExpertData, + getNewData: (locale: string, token: string) => Promise, + errorMessage?: string; +}; + +export const ExpertProfile = ({ locale, data, updateData, isFull }: ExpertProfileProps) => { const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); const [loading, setLoading] = useState<(keyof ExpertData)[]>([]); + function getNewPartData ({ key, getNewData, errorMessage = 'Не удалось получить данные' }: NewDataPartProps) { + setLoading([key]); + getNewData(locale, jwt) + .then((newData) => { + updateData({ + ...data, + [key]: newData + }); + }) + .catch(() => message.error(errorMessage)) + .finally(() => setLoading([])); + } + const updateExpert = (key: keyof ExpertData) => { switch (key) { case 'tags': - setLoading([key]); - getTags(locale, jwt) - .then((tags) => { - updateData({ - ...data, - tags - }); - }) - .catch(() => message.error('Не удалось обновить направления')) - .finally(() => setLoading([])); + getNewPartData({ + key, + getNewData: getTags, + errorMessage: 'Не удалось получить направления' + }); + break; + case 'practice': + getNewPartData({ + key, + getNewData: getPractice + }); + break; + case 'education': + getNewPartData({ + key, + getNewData: getEducation, + errorMessage: 'Не удалось получить информацию об образовании' + }); + break; + case 'schedule': + getNewPartData({ + key, + getNewData: getSchedule, + errorMessage: 'Не удалось получить расписание' + }); + break; + case 'person': + getNewPartData({ + key, + getNewData: getPersonalData, + errorMessage: 'Не удалось получить информацию о пользователе' + }); + break; + case 'payData': + getNewPartData<{ person6Data?: PayInfo }>({ + key, + getNewData: getPayData, + errorMessage: 'Не удалось получить платежную информацию' + }); break; default: break; @@ -52,36 +105,39 @@ export const ExpertProfile = ({ locale, data, updateData }: ExpertProfileProps)
    - +
    -
    +
    - David + {`${data?.person?.username} ${data?.person?.surname || ''}`}
    -
    -
    -
    -
    -
    -

    {i18nText('aboutCoach', locale)}

    -

    person1 + person4

    - } + {!isFull && ( + +
  5. о себе
  6. +
  7. темы сессии
  8. +
  9. рабочее расписание
  10. +
  11. информация об образовании
  12. +
  13. платежная информация
  14. + + )} + type="warning" + showIcon /> -
    -
    - {`12 ${i18nText('practiceHours', locale)}`} -
    -
    - {`15 ${i18nText('supervisionCount', locale)}`} -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. -
    + )}
    + + + + - - + + + + + +
    - ) + ); }; diff --git a/src/components/ExpertProfile/content/ExpertAbout.tsx b/src/components/ExpertProfile/content/ExpertAbout.tsx new file mode 100644 index 0000000..0d1cdf5 --- /dev/null +++ b/src/components/ExpertProfile/content/ExpertAbout.tsx @@ -0,0 +1,81 @@ +'use client' + +import {useState} from "react"; +import {Tag} from "antd"; +import {EditOutlined} from "@ant-design/icons"; +import {LinkButton} from "../../view/LinkButton"; +import {ExpertData, ProfileData} from "../../../types/profile"; +import {i18nText} from "../../../i18nKeys/index"; +import {PracticeDTO} from "../../../types/practice"; +import {ExpertPractice} from "../../Experts/ExpertDetails"; +import {EditExpertAboutModal} from "../../Modals/EditExpertAboutModal"; + +type ExpertAboutProps = { + locale: string; + practice?: PracticeDTO; + person?: ProfileData; + updateExpert: (key: keyof ExpertData) => void; +}; + +export const ExpertAbout = ({ locale, updateExpert, practice, person }: ExpertAboutProps) => { + const [showEdit, setShowEdit] = useState(false); + + const supervisionCount = practice?.person4Data?.supervisionPerYears && practice?.person4Data?.supervisionPerYearId + ? practice.person4Data.supervisionPerYears.filter(({ id }) => id === practice.person4Data.supervisionPerYearId) + : []; + + return ( +
    +
    +
    +

    {i18nText('aboutCoach', locale)}

    + } + onClick={() => setShowEdit(true)} + /> +
    +
    +
    + {`${practice?.person4Data?.practiceHours || 0} ${i18nText('practiceHours', locale)} | ${supervisionCount.length > 0 ? supervisionCount[0].name : 0} ${i18nText('supervisionCount', locale)}`} +
    +
    + {practice?.person4Data?.sessionCost && ( +
    +
    {i18nText('price', locale)}
    +
    {`${practice?.person4Data?.sessionCost} €`}
    +
    + )} + {practice?.person4Data?.sessionDuration && ( +
    +
    {i18nText('duration', locale)}
    +
    {`${practice?.person4Data?.sessionDuration} ${locale === 'ru' ? 'мин' : 'min'}`}
    +
    + )} +
    +
    +
    {i18nText('sessionLang', locale)}
    +
    + {person?.languagesLinks && person.languagesLinks?.length > 0 && person.languagesLinks + .map(({ language: { code, nativeSpelling } }) => {nativeSpelling})} +
    +
    + +
    +
    + setShowEdit(false)} + refreshPractice={() => updateExpert('practice')} + refreshPerson={() => updateExpert('person')} + /> +
    + ); +}; diff --git a/src/components/ExpertProfile/content/ExpertEducation.tsx b/src/components/ExpertProfile/content/ExpertEducation.tsx index 8ddc1e6..210d2c8 100644 --- a/src/components/ExpertProfile/content/ExpertEducation.tsx +++ b/src/components/ExpertProfile/content/ExpertEducation.tsx @@ -1,65 +1,154 @@ +'use client' + import { EditOutlined } from '@ant-design/icons'; import { EducationDTO } from '../../../types/education'; import { i18nText } from '../../../i18nKeys'; import { LinkButton } from '../../view/LinkButton'; +import {ExpertCertificate} from "../../Experts/ExpertDetails"; +import {useState} from "react"; +import {ExpertData} from "../../../types/profile"; +import {EditExpertEducationModal} from "../../Modals/EditExpertEducationModal"; type ExpertEducationProps = { locale: string; data?: EducationDTO; + updateExpert: (key: keyof ExpertData) => void; }; -export const ExpertEducation = ({ locale, data }: ExpertEducationProps) => { +export const ExpertEducation = ({ locale, data, updateExpert }: ExpertEducationProps) => { + const [showEdit, setShowEdit] = useState(false); + + const getAssociationLevel = (accLevelId?: number) => { + if (accLevelId) { + const [cur] = (data?.associationLevels || []).filter(({ id }) => id === accLevelId) || []; + + return cur?.name || ''; + } + + return ''; + }; + + const getAssociation = (accLevelId?: number) => { + if (accLevelId) { + const [curLevel] = (data?.associationLevels || []).filter(({ id }) => id === accLevelId) || []; + if (curLevel) { + const [cur] = (data?.associations || []).filter(({ id }) => id === curLevel.associationId) || []; + return cur?.name || ''; + } + } + + return ''; + }; + return (
    -

    {i18nText('education', locale)}

    -

    person2

    +

    {i18nText('skillsInfo', locale)}

    } + onClick={() => setShowEdit(true)} />
    -
    -

    Psychologist

    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. + {data?.person2Data?.educations?.length > 0 && ( +
    + {data?.person2Data?.educations?.map(({ id, title, description, document }) => ( +
    +

    {title}

    + {description &&
    {description}
    } + {document && ( +
    + +
    + )} +
    + ))}
    -
    - + )} +
    + {data?.person2Data?.certificates?.length > 0 && ( +
    +

    {i18nText('profCertification', locale)}

    +
    + {data?.person2Data?.certificates?.map((cert) => ( +
    +
    + {`${getAssociationLevel(cert?.associationLevelId)} ${getAssociation(cert?.associationLevelId)}`} +
    + {cert.document && ( +
    + +
    + )} +
    + ))}
    -
    -
    -

    {i18nText('profCertification', locale)}

    -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. + )} + {data?.person2Data?.trainings?.length > 0 && ( +
    +

    + {`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`} +

    +
    + {data?.person2Data?.trainings?.map(({ id, title, description, document }) => ( +
    +

    {title}

    + {description &&
    {description}
    } + {document && ( +
    + +
    + )} +
    + ))}
    -
    -
    -

    - {`${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`} -

    -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. + )} + {data?.person2Data?.mbas?.length > 0 && ( +
    +

    {i18nText('mba', locale)}

    +
    + {data?.person2Data?.mbas?.map(({ id, title, description, document }) => ( +
    +

    {title}

    + {description &&
    {description}
    } + {document && ( +
    + +
    + )} +
    + ))}
    -
    -
    -

    {i18nText('mba', locale)}

    -
    -
    - Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam aliquet, lectus nec viverra - malesuada, ligula sem tempor risus, non posuere urna diam a libero. + )} + {data?.person2Data?.experiences?.length > 0 && ( +
    +

    {i18nText('mExperiences', locale)}

    +
    + {data?.person2Data?.experiences?.map(({ id, title, description, document }) => ( +
    +

    {title}

    + {description &&
    {description}
    } + {document && ( +
    + +
    + )} +
    + ))}
    -
    + )} + setShowEdit(false)} + locale={locale} + data={data} + refresh={() => updateExpert('education')} + />
    ); }; diff --git a/src/components/ExpertProfile/content/ExpertPayData.tsx b/src/components/ExpertProfile/content/ExpertPayData.tsx index 648aed2..6eeaf27 100644 --- a/src/components/ExpertProfile/content/ExpertPayData.tsx +++ b/src/components/ExpertProfile/content/ExpertPayData.tsx @@ -1,28 +1,65 @@ +'use client' + +import { useState } from 'react'; import { EditOutlined } from '@ant-design/icons'; import { i18nText } from '../../../i18nKeys'; -import { PayInfo } from '../../../types/profile'; +import { ExpertData, PayInfo } from '../../../types/profile'; import { LinkButton } from '../../view/LinkButton'; +import { EditExpertPayDataModal } from '../../Modals/EditExpertPayDataModal'; type ExpertPayDataProps = { locale: string; - data?: PayInfo + data?: PayInfo; + updateExpert: (key: keyof ExpertData) => void; }; -export const ExpertPayData = ({ locale, data }: ExpertPayDataProps) => { +export const ExpertPayData = ({ locale, data, updateExpert }: ExpertPayDataProps) => { + const [showEdit, setShowEdit] = useState(false); + + const hide = (str?: string) => { + const reg = new RegExp('(.)(?=.*....)', 'gi'); + return str ? str.replace(reg, '*') : ''; + } + return (
    -

    Card data - person6

    +

    {i18nText('payInfo', locale)}

    } + onClick={() => setShowEdit(true)} />
    -
    - Card +
    + {data?.beneficiaryName && ( +
    +
    {i18nText('beneficiaryName', locale)}
    +
    {data.beneficiaryName}
    +
    + )} + {data?.bicOrSwift && ( +
    +
    {i18nText('bicOrSwift', locale)}
    +
    {hide(data.bicOrSwift)}
    +
    + )} + {data?.iban && ( +
    +
    IBAN
    +
    {hide(data.iban)}
    +
    + )}
    + setShowEdit(false)} + refresh={() => updateExpert('payData')} + />
    ); }; diff --git a/src/components/ExpertProfile/content/ExpertSchedule.tsx b/src/components/ExpertProfile/content/ExpertSchedule.tsx index 907aa22..33d5285 100644 --- a/src/components/ExpertProfile/content/ExpertSchedule.tsx +++ b/src/components/ExpertProfile/content/ExpertSchedule.tsx @@ -1,7 +1,11 @@ import { EditOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; import { ScheduleDTO } from '../../../types/schedule'; import { i18nText } from '../../../i18nKeys'; import { LinkButton } from '../../view/LinkButton'; +import {useState} from "react"; +import {Tag} from "antd"; +import {getCurrentTime} from "../../../utils/time"; type ExpertScheduleProps = { locale: string; @@ -9,18 +13,34 @@ type ExpertScheduleProps = { }; export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => { + const [showEdit, setShowEdit] = useState(false); + // person51 + return (
    -

    Schedule - person51

    - {i18nText('schedule', locale)} + {/*} - /> + onClick={() => setShowEdit(true)} + />*/}
    -
    - Schedule +
    + {data && data?.workingTimes?.map((date, index) => { + const { startDay, startTime, endDay, endTime } = getCurrentTime(date); + + return ( +
    + {i18nText(startDay, locale)} +
    {startTime}
    + - + {startDay !== endDay && {i18nText(endDay, locale)}} +
    {endTime}
    +
    + ) + })}
    diff --git a/src/components/ExpertProfile/content/ExpertTags.tsx b/src/components/ExpertProfile/content/ExpertTags.tsx index 7441bed..1e9d27a 100644 --- a/src/components/ExpertProfile/content/ExpertTags.tsx +++ b/src/components/ExpertProfile/content/ExpertTags.tsx @@ -22,7 +22,7 @@ export const ExpertTags = ({ locale, data, updateExpert }: ExpertTagsProps) => {
    -

    {i18nText('direction', locale)}

    +

    {i18nText('topics', locale)}

    } diff --git a/src/components/Experts/ExpertDetails.tsx b/src/components/Experts/ExpertDetails.tsx index e2c942c..fec8476 100644 --- a/src/components/Experts/ExpertDetails.tsx +++ b/src/components/Experts/ExpertDetails.tsx @@ -4,7 +4,8 @@ import React, { FC } from 'react'; import Image from 'next/image'; import { Tag, Image as AntdImage, Space } from 'antd'; import { ZoomInOutlined, ZoomOutOutlined, StarFilled } from '@ant-design/icons'; -import { ExpertDetails, ExpertDocument } from '../../types/experts'; +import { ExpertDetails, Practice, ThemeGroup } from '../../types/experts'; +import { ExpertDocument } from '../../types/file'; import { Locale } from '../../types/locale'; import { CustomRate } from '../view/CustomRate'; import { i18nText } from '../../i18nKeys'; @@ -15,6 +16,12 @@ type ExpertDetailsProps = { locale?: string; }; +type ExpertPracticeProps = { + cases?: Practice[]; + themes?: ThemeGroup[]; + locale?: string; +}; + export const ExpertCard: FC = ({ expert, locale }) => { const { publicCoachDetails } = expert || {}; @@ -62,10 +69,10 @@ export const ExpertInformation: FC = ({ expert, locale }) =>
    {/*

    {}

    */}
    - {coachLanguages?.map((skill) => {skill})} + {coachLanguages?.map((lang) => {lang})}
    -

    + {/*

    Hello, my name is Marcelo. I am a Senior UX Designer with more than 6 years of experience working with the largest companies in the world such as Disney, Globant and currently IBM. During my career, I have helped organizations solve complex problems using aesthetically pleasing @@ -79,7 +86,7 @@ export const ExpertInformation: FC = ({ expert, locale }) => Strategic thinking

    Oh, and I also speak Spanish! -

    +

    */}
    {tags?.map((skill) => {skill?.name})}
    @@ -93,14 +100,12 @@ export const ExpertInformation: FC = ({ expert, locale }) => ); }; -export const ExpertPractice: FC = ({ expert, locale }) => { - const { publicCoachDetails: { practiceCases = [], themesGroups = [] } } = expert || {}; - - return practiceCases?.length > 0 ? ( +export const ExpertPractice: FC = ({ themes = [], cases = [], locale }) => { + return cases?.length > 0 ? (

    {i18nText('successfulCase', locale)}

    - {practiceCases?.map(({ id, description, themesGroupIds }) => { - const filtered = themesGroups?.filter(({ id }) => themesGroupIds?.includes(+id)); + {cases?.map(({ id, description, themesGroupIds }) => { + const filtered = themes ? themes.filter(({ id }) => themesGroupIds?.includes(+id)) : []; return (
    diff --git a/src/components/Experts/Filter.tsx b/src/components/Experts/Filter.tsx index 7a548ae..debb1d1 100644 --- a/src/components/Experts/Filter.tsx +++ b/src/components/Experts/Filter.tsx @@ -162,18 +162,15 @@ export const ExpertsFilter = ({ ), [filter, searchParams, searchData]); const getLangList = () => { - const reg = searchLang ? new RegExp(searchLang, 'ig') : ''; - const langList = reg ? (languages || []).filter(({ code, nativeSpelling }) => reg.test(code) || reg.test(nativeSpelling)) : languages; + const langList = searchLang ? (languages || []).filter(({ code, nativeSpelling }) => code.indexOf(searchLang) !== -1 || nativeSpelling.indexOf(searchLang) !== -1) : languages; return langList?.length ? getList('userLanguages', langList.map(({ code, nativeSpelling }) => ({ id: code, name: nativeSpelling }))) : null; }; const getTagsList = () => { - const reg = searchTags ? new RegExp(searchTags, 'ig') : ''; - - if (reg) { - const tagsList = filteredTags.filter(({ name, group }) => reg.test(name) || reg.test(group)); + if (searchTags) { + const tagsList = filteredTags.filter(({ name, group }) => name.indexOf(searchTags) !== -1 || group.indexOf(searchTags) !== -1); return getList('themesTagIds', tagsList); } diff --git a/src/components/Modals/EditExpertAboutModal.tsx b/src/components/Modals/EditExpertAboutModal.tsx new file mode 100644 index 0000000..a17c8a0 --- /dev/null +++ b/src/components/Modals/EditExpertAboutModal.tsx @@ -0,0 +1,254 @@ +'use client'; + +import React, { FC, useEffect, useState } from 'react'; +import { Modal, Button, message, Form, Input } from 'antd'; +import { CloseOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { ProfileData, ProfileRequest } from '../../types/profile'; +import { PracticePersonData, PracticeDTO, PracticeData, PracticeCase } from '../../types/practice'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { setPersonData, setPractice } from '../../actions/profile'; +import { CustomInput } from '../view/CustomInput'; +import { CustomMultiSelect } from '../view/CustomMultiSelect'; +import { CustomSelect } from '../view/CustomSelect'; +import { LinkButton } from '../view/LinkButton'; + +type EditExpertAboutModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + practice?: PracticeDTO; + person?: ProfileData; + refreshPractice: () => void; + refreshPerson: () => void; +}; + +type FormPerson = PracticePersonData & { + sessionLang: number[]; +}; + +export const EditExpertAboutModal: FC = ({ + open, + handleCancel, + locale, + practice, + person, + refreshPerson, + refreshPractice +}) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [practiceCases, setPracticeCases] = useState([]); + + useEffect(() => { + if (open) { + if (practice?.person4Data) { + form.setFieldsValue(practice.person4Data); + + setPracticeCases(practice.person4Data?.practiceCases || []); + } + if (person?.languagesLinks) { + form.setFieldValue('sessionLang', person.languagesLinks.map(({ languageId }) => languageId)) + } + } + }, [open, practice?.person4Data]); + + const addPracticeCase = () => { + setPracticeCases([ + ...practiceCases, + { + description: '', + themesGroupIds: [] + } + ]); + }; + + const deletePracticeCase = (index: number) => { + setPracticeCases([...practiceCases].filter((cases, i) => i !== index)); + }; + + const onChangePracticeDescription = (value: string, index: number) => { + setPracticeCases(practiceCases.map((cases, i) => { + if (i === index) { + return { + ...cases, + description: value + } + } + + return cases; + })); + }; + + const onChangePracticeThemes = (value: number[], index: number) => { + setPracticeCases(practiceCases.map((cases, i) => { + if (i === index) { + return { + ...cases, + themesGroupIds: value + } + } + + return cases; + })); + }; + + const onSave = () => { + form.validateFields().then((values) => { + const newPersonData: ProfileRequest = { + login: person?.login, + isPasswordKeepExisting: true, + username: person?.username, + surname: person?.surname, + isFaceImageKeepExisting: true, + phone: person?.phone, + languagesLinks: values?.sessionLang?.map((id) => ({ languageId: +id })) || [] + }; + + const newPracticeData: PracticeData = { + practiceHours: values?.practiceHours, + supervisionPerYearId: values?.supervisionPerYearId, + sessionDuration: values?.sessionDuration ? (isNaN(Number(values.sessionDuration)) ? 0 : Number(values.sessionDuration)) : 0, + sessionCost: values?.sessionCost ? (isNaN(Number(values.sessionCost)) ? 0 : Number(values.sessionCost)) : 0, + practiceCases: practiceCases ? practiceCases : practice?.person4Data?.practiceCases + } + + setLoading(true); + Promise.all([ + setPractice(locale, jwt, newPracticeData), + setPersonData(newPersonData, locale, jwt) + ]) + .then(() => { + handleCancel(); + refreshPractice(); + refreshPerson(); + }) + .catch(() => { + message.error('Не удалось сохранить данные'); + }) + .finally(() => { + setLoading(false); + }) + }) + }; + + return ( + } + > +
    +
    {i18nText('aboutCoach', locale)}
    +
    + + + ({ value: id, label: nativeSpelling })) || []} + /> + + + + + + ({ value: cost, label: cost })) || []} + /> + + + + + + ({ value: id, label: name })) || []} + /> + + +
    +
    +
    +
    {i18nText('successfulCase', locale)}
    + } + onClick={addPracticeCase} + /> +
    + {practiceCases.map(({ description, themesGroupIds }, index) => ( +
    +
    +
    + ({ value: id, label: name })) || []} + onChange={(val) => onChangePracticeThemes(val, index)} + /> +
    +
    + onChangePracticeDescription(e.target.value, index)} + /> +
    +
    + } + onClick={() => deletePracticeCase(index)} + /> +
    + ))} +
    +
    + +
    +
    +
    + ); +}; diff --git a/src/components/Modals/EditExpertEducationModal.tsx b/src/components/Modals/EditExpertEducationModal.tsx new file mode 100644 index 0000000..e2720e5 --- /dev/null +++ b/src/components/Modals/EditExpertEducationModal.tsx @@ -0,0 +1,152 @@ +'use client'; + +import React, { FC, useEffect, useState } from 'react'; +import {Modal, Button, message, Form, Collapse, GetProp, UploadProps} from 'antd'; +import type { CollapseProps } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { PracticePersonData, PracticeDTO, PracticeData, PracticeCase } from '../../types/practice'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import {setEducation} from '../../actions/profile'; +import {Certificate, Details, EducationData, EducationDTO, Experience} from "../../types/education"; +import {CertificatesContent} from "./educationModalContent/Certificates"; +import {EducationsContent} from "./educationModalContent/Educations"; +import {TrainingsContent} from "./educationModalContent/Trainings"; +import {MbasContent} from "./educationModalContent/Mbas"; +import {ExperiencesContent} from "./educationModalContent/Experiences"; + +type EditExpertEducationModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: EducationDTO; + refresh: () => void; +}; + +type FormPerson = PracticePersonData & { + sessionLang: number[]; +}; + +export const EditExpertEducationModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh +}) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + const [editedData, setEditedData] = useState(data?.person2Data as EducationData); + + const onSave = () => { + setLoading(true); + setEducation(locale, jwt, editedData) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить образование'); + }) + .finally(() => { + setLoading(false); + }) + }; + + const items: CollapseProps['items'] = [ + { + key: 'certificates', + label: i18nText('profCertification', locale), + children: ( + setEditedData({ ...editedData, certificates })} + locale={locale} + associationLevels={data?.associationLevels} + associations={data?.associations} + /> + ), + }, + { + key: 'educations', + label: i18nText('education', locale), + children: ( + setEditedData({ ...editedData, educations })} + locale={locale} + /> + ), + }, + { + key: 'trainings', + label: `${i18nText('trainings', locale)} | ${i18nText('seminars', locale)} | ${i18nText('courses', locale)}`, + children: ( + setEditedData({ ...editedData, trainings })} + locale={locale} + /> + ), + }, + { + key: 'mbas', + label: i18nText('mba', locale), + children: ( + setEditedData({ ...editedData, mbas })} + locale={locale} + /> + ), + }, + { + key: 'experiences', + label: i18nText('mExperiences', locale), + children: ( + setEditedData({ ...editedData, experiences })} + locale={locale} + /> + ), + }, + ]; + + return ( + } + > +
    +
    {i18nText('skillsInfo', locale)}
    +
    +
    + + +
    +
    + +
    +
    +
    + ); +}; diff --git a/src/components/Modals/EditExpertPayDataModal.tsx b/src/components/Modals/EditExpertPayDataModal.tsx new file mode 100644 index 0000000..be226e7 --- /dev/null +++ b/src/components/Modals/EditExpertPayDataModal.tsx @@ -0,0 +1,118 @@ +'use client'; + +import React, { FC, useEffect, useState } from 'react'; +import { Modal, Button, message, Form } from 'antd'; +import { CloseOutlined } from '@ant-design/icons'; +import { i18nText } from '../../i18nKeys'; +import { PayInfo } from '../../types/profile'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { setPayData } from '../../actions/profile'; +import { CustomInput } from '../view/CustomInput'; + +type EditExpertPayDataModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: PayInfo; + refresh: () => void; +}; + +export const EditExpertPayDataModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh +}) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [loading, setLoading] = useState(false); + const [form] = Form.useForm(); + + useEffect(() => { + if (open) { + if (data) { + form.setFieldsValue(data); + } else { + form.resetFields(); + } + } + }, [open, data]); + + const onSavePayData = () => { + form.validateFields().then(({ beneficiaryName, bicOrSwift, iban }) => { + setLoading(true); + setPayData(locale, jwt, { beneficiaryName, bicOrSwift, iban }) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить платежную информацию'); + }) + .finally(() => { + setLoading(false); + }) + }) + }; + + return ( + } + > +
    +
    {i18nText('payInfo', locale)}
    +
    +
    + + + + + + + + + +
    +
    +
    + +
    +
    +
    + ); +}; diff --git a/src/components/Modals/EditExpertTagsModal.tsx b/src/components/Modals/EditExpertTagsModal.tsx index 9b1677a..2d56b98 100644 --- a/src/components/Modals/EditExpertTagsModal.tsx +++ b/src/components/Modals/EditExpertTagsModal.tsx @@ -69,7 +69,7 @@ export const EditExpertTagsModal: FC = ({ closeIcon={} >
    -
    {i18nText('direction', locale)}
    +
    {i18nText('selectTopic', locale)}
    {data?.themesGroups && data.themesGroups.filter(({ isActive }) => isActive).map(({ id, name }) => (
    diff --git a/src/components/Modals/educationModalContent/Certificates.tsx b/src/components/Modals/educationModalContent/Certificates.tsx new file mode 100644 index 0000000..b0acdc1 --- /dev/null +++ b/src/components/Modals/educationModalContent/Certificates.tsx @@ -0,0 +1,205 @@ +import { Upload, UploadFile } from 'antd'; +import { DeleteOutlined } from '@ant-design/icons'; +import { Association, AssociationLevel, Certificate } from '../../../types/education'; +import { CustomSelect } from '../../view/CustomSelect'; +import { LinkButton } from '../../view/LinkButton'; +import { OutlinedButton } from '../../view/OutlinedButton'; +import { i18nText } from '../../../i18nKeys'; +import { validateDoc } from '../../../utils/account'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../../constants/common'; + +type CertificatesContentProps = { + certificates?: Certificate[]; + update: (cert?: Certificate[]) => void; + associations?: Association[]; + associationLevels?: AssociationLevel[]; + locale: string; +}; + +export const CertificatesContent = ({ + certificates, + update, + associations, + associationLevels, + locale +}: CertificatesContentProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + + const addCertificate = () => { + const cert = { + associationLevelId: undefined, + document: null + }; + + update(certificates?.length > 0 + ? [ + ...certificates, + cert + ] + : [cert]); + }; + + const deleteCertificate = (index: number) => { + update([...certificates].filter((cert, i) => i !== index)); + }; + + const beforeUpload = (file: UploadFile) => { + const isValid = validateDoc(file); + + if (!isValid) { + return Upload.LIST_IGNORE; + } + + return true; + } + + const onRemoveFile = (index: number) => { + update(certificates?.map((cert, i) => { + if (i === index) { + return { + ...cert, + document: null, + } + } + + return cert; + })); + }; + + const onChangeAssociation = (val: number, index: number) => { + update(certificates?.map((cert, i) => { + if (i === index) { + return { + ...cert, + associationId: val, + associationLevelId: undefined + } + } + + return cert; + })); + }; + + const onChangeLevel = (val: number, index: number) => { + update(certificates?.map((cert, i) => { + if (i === index) { + return { + ...cert, + associationLevelId: val + } + } + + return cert; + })); + }; + + const onChange = (file: any, index: number) => { + if (file?.response) { + update([...certificates].map((cert, i) => { + if (i === index) { + return { + ...cert, + document: file?.response || null, + } + } + + return cert; + })); + } + }; + + return ( +
    +
    + {certificates?.map(({ associationId, associationLevelId, document: file }, index) => { + let cAssociationId = associationId; + + if (!cAssociationId) { + const [cAssLvl] = associationLevels ? associationLevels.filter(({ id }) => id === associationLevelId) : []; + + if (cAssLvl?.associationId) { + cAssociationId = associations ? associations.filter(({ id }) => id === cAssLvl.associationId)[0]?.id : undefined; + } + } + + return ( +
    +
    + ({ value: id, label: name })) || []} + onChange={(val) => onChangeAssociation(val, index)} + style={{ maxWidth: 320, minWidth: 320 }} + /> + 0 + ? associationLevels + .filter(({ associationId }) => associationId === cAssociationId) + .map(({ id, name }) => ({ value: id, label: name })) + : []} + onChange={(val) => onChangeLevel(val, index)} + /> + {/* beforeUpload(file as UploadFile, index)} + multiple={false} + onRemove={() => onRemoveFile(index)} + > + {i18nText('addDiploma', locale)} + */} + onRemoveFile(index)} + action="https://api.bbuddy.expert/api/home/uploadfile" + method="POST" + headers={{ + authorization: `Bearer ${jwt}`, + 'X-User-Language': locale, + 'X-Referrer-Channel': 'site', + }} + onChange={(obj) => onChange(obj.file, index)} + > + {i18nText('addDiploma', locale)} + +
    + } + onClick={() => deleteCertificate(index)} + /> +
    + ) + })} +
    + + {i18nText('addNew', locale)} + +
    + ); +}; diff --git a/src/components/Modals/educationModalContent/Educations.tsx b/src/components/Modals/educationModalContent/Educations.tsx new file mode 100644 index 0000000..33bae54 --- /dev/null +++ b/src/components/Modals/educationModalContent/Educations.tsx @@ -0,0 +1,166 @@ +import { DeleteOutlined } from '@ant-design/icons'; +import { CustomInput } from '../../view/CustomInput'; +import { LinkButton } from '../../view/LinkButton'; +import { OutlinedButton } from '../../view/OutlinedButton'; +import { Details } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import { Upload, UploadFile } from 'antd'; +import { validateDoc } from '../../../utils/account'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../../constants/common'; + +type EducationsContentProps = { + educations?: Details[]; + update: (edu?: Details[]) => void; + locale: string; +}; + +export const EducationsContent = ({ + educations, + update, + locale +}: EducationsContentProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + + const addEdu = () => { + const edu = { + title: undefined, + description: undefined, + document: null + }; + + update(educations?.length > 0 + ? [ + ...educations, + edu + ] + : [edu]); + }; + + const deleteEdu = (index: number) => { + update([...educations].filter((ed, i) => i !== index)); + }; + + const beforeUpload = (file: UploadFile) => { + const isValid = validateDoc(file); + + if (!isValid) { + return Upload.LIST_IGNORE; + } + + return true; + } + + const onRemoveFile = (index: number) => { + update(educations?.map((edu, i) => { + if (i === index) { + return { + ...edu, + document: null, + } + } + + return edu; + })); + }; + + const onChange = (file: any, index: number) => { + if (file?.response) { + update([...educations].map((edu, i) => { + if (i === index) { + return { + ...edu, + document: file?.response || null, + } + } + + return edu; + })); + } + }; + + const onChangeUniversity = (val: string, index: number) => { + update(educations?.map((edu, i) => { + if (i === index) { + return { + ...edu, + title: val, + } + } + + return edu; + })); + }; + + const onChangeDesc = (val: string, index: number) => { + update(educations?.map((edu, i) => { + if (i === index) { + return { + ...edu, + description: val, + } + } + + return edu; + })); + }; + + return ( +
    +
    + {educations?.map(({ title, description, document: file}, index) => ( +
    +
    + onChangeUniversity(e?.target?.value, index)} + /> + onChangeDesc(e?.target?.value, index)} + /> + onRemoveFile(index)} + action="https://api.bbuddy.expert/api/home/uploadfile" + method="POST" + headers={{ + authorization: `Bearer ${jwt}`, + 'X-User-Language': locale, + 'X-Referrer-Channel': 'site', + }} + onChange={(obj) => onChange(obj.file, index)} + > + {i18nText('addDiploma', locale)} + +
    + } + onClick={() => deleteEdu(index)} + /> +
    + ))} +
    + + {i18nText('addNew', locale)} + +
    + ); +}; diff --git a/src/components/Modals/educationModalContent/Experiences.tsx b/src/components/Modals/educationModalContent/Experiences.tsx new file mode 100644 index 0000000..4a71b38 --- /dev/null +++ b/src/components/Modals/educationModalContent/Experiences.tsx @@ -0,0 +1,101 @@ +import { DeleteOutlined } from '@ant-design/icons'; +import { CustomInput } from '../../view/CustomInput'; +import { LinkButton } from '../../view/LinkButton'; +import { OutlinedButton } from '../../view/OutlinedButton'; +import { Experience } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import {useLocalStorage} from "../../../hooks/useLocalStorage"; +import {AUTH_TOKEN_KEY} from "../../../constants/common"; + +type ExperiencesContentProps = { + experiences?: Experience[]; + update: (ex?: Experience[]) => void; + locale: string; +}; + +export const ExperiencesContent = ({ + experiences, + update, + locale +}: ExperiencesContentProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + + const addExperience = () => { + const ex = { + title: undefined, + description: undefined, + }; + + update(experiences?.length > 0 + ? [ + ...experiences, + ex + ] + : [ex]); + }; + + const deleteExperience = (index: number) => { + update([...experiences].filter((ex, i) => i !== index)); + }; + + const onChangeName = (val: string, index: number) => { + update(experiences?.map((ex, i) => { + if (i === index) { + return { + ...ex, + title: val, + } + } + + return ex; + })); + }; + + const onChangeDesc = (val: string, index: number) => { + update(experiences?.map((ex, i) => { + if (i === index) { + return { + ...ex, + description: val, + } + } + + return ex; + })); + }; + + return ( +
    +
    + {experiences?.map(({ title, description}, index) => ( +
    +
    + onChangeName(e?.target?.value, index)} + /> + onChangeDesc(e?.target?.value, index)} + /> +
    + } + onClick={() => deleteExperience(index)} + /> +
    + ))} +
    + + {i18nText('addNew', locale)} + +
    + ); +}; diff --git a/src/components/Modals/educationModalContent/Mbas.tsx b/src/components/Modals/educationModalContent/Mbas.tsx new file mode 100644 index 0000000..d119781 --- /dev/null +++ b/src/components/Modals/educationModalContent/Mbas.tsx @@ -0,0 +1,166 @@ +import { DeleteOutlined } from '@ant-design/icons'; +import { CustomInput } from '../../view/CustomInput'; +import { LinkButton } from '../../view/LinkButton'; +import { OutlinedButton } from '../../view/OutlinedButton'; +import { Details } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import { Upload, UploadFile } from 'antd'; +import { validateDoc } from '../../../utils/account'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../../constants/common'; + +type MbasContentProps = { + mbas?: Details[]; + update: (mba?: Details[]) => void; + locale: string; +}; + +export const MbasContent = ({ + mbas, + update, + locale +}: MbasContentProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + + const addMba = () => { + const mba = { + title: undefined, + description: undefined, + document: null + }; + + update(mbas?.length > 0 + ? [ + ...mbas, + mba + ] + : [mba]); + }; + + const deleteMba = (index: number) => { + update([...mbas].filter((mba, i) => i !== index)); + }; + + const beforeUpload = (file: UploadFile) => { + const isValid = validateDoc(file); + + if (!isValid) { + return Upload.LIST_IGNORE; + } + + return true; + } + + const onRemoveFile = (index: number) => { + update(mbas?.map((mb, i) => { + if (i === index) { + return { + ...mb, + document: null, + } + } + + return mb; + })); + }; + + const onChange = (file: any, index: number) => { + if (file?.response) { + update([...mbas].map((mb, i) => { + if (i === index) { + return { + ...mb, + document: file?.response || null, + } + } + + return mb; + })); + } + }; + + const onChangeName = (val: string, index: number) => { + update(mbas?.map((mb, i) => { + if (i === index) { + return { + ...mb, + title: val, + } + } + + return mb; + })); + }; + + const onChangeDesc = (val: string, index: number) => { + update(mbas?.map((mb, i) => { + if (i === index) { + return { + ...mb, + description: val, + } + } + + return mb; + })); + }; + + return ( +
    +
    + {mbas?.map(({ title, description, document: file}, index) => ( +
    +
    + onChangeName(e?.target?.value, index)} + /> + onChangeDesc(e?.target?.value, index)} + /> + onRemoveFile(index)} + action="https://api.bbuddy.expert/api/home/uploadfile" + method="POST" + headers={{ + authorization: `Bearer ${jwt}`, + 'X-User-Language': locale, + 'X-Referrer-Channel': 'site', + }} + onChange={(obj) => onChange(obj.file, index)} + > + {i18nText('addDiploma', locale)} + +
    + } + onClick={() => deleteMba(index)} + /> +
    + ))} +
    + + {i18nText('addNew', locale)} + +
    + ); +}; diff --git a/src/components/Modals/educationModalContent/Trainings.tsx b/src/components/Modals/educationModalContent/Trainings.tsx new file mode 100644 index 0000000..9555784 --- /dev/null +++ b/src/components/Modals/educationModalContent/Trainings.tsx @@ -0,0 +1,166 @@ +import { DeleteOutlined } from '@ant-design/icons'; +import { CustomInput } from '../../view/CustomInput'; +import { LinkButton } from '../../view/LinkButton'; +import { OutlinedButton } from '../../view/OutlinedButton'; +import { Details } from '../../../types/education'; +import { i18nText } from '../../../i18nKeys'; +import { Upload, UploadFile } from 'antd'; +import { validateDoc } from '../../../utils/account'; +import { useLocalStorage } from '../../../hooks/useLocalStorage'; +import { AUTH_TOKEN_KEY } from '../../../constants/common'; + +type TrainingsContentProps = { + trainings?: Details[]; + update: (tr?: Details[]) => void; + locale: string; +}; + +export const TrainingsContent = ({ + trainings, + update, + locale +}: TrainingsContentProps) => { + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + + const addTrainings = () => { + const training = { + title: undefined, + description: undefined, + document: null + }; + + update(trainings?.length > 0 + ? [ + ...trainings, + training + ] + : [training]); + }; + + const deleteTrainings = (index: number) => { + update([...trainings].filter((tr, i) => i !== index)); + }; + + const beforeUpload = (file: UploadFile) => { + const isValid = validateDoc(file); + + if (!isValid) { + return Upload.LIST_IGNORE; + } + + return true; + } + + const onRemoveFile = (index: number) => { + update(trainings?.map((tr, i) => { + if (i === index) { + return { + ...tr, + document: null, + } + } + + return tr; + })); + }; + + const onChange = (file: any, index: number) => { + if (file?.response) { + update([...trainings].map((tr, i) => { + if (i === index) { + return { + ...tr, + document: file?.response || null, + } + } + + return tr; + })); + } + }; + + const onChangeName = (val: string, index: number) => { + update(trainings?.map((tr, i) => { + if (i === index) { + return { + ...tr, + title: val, + } + } + + return tr; + })); + }; + + const onChangeDesc = (val: string, index: number) => { + update(trainings?.map((tr, i) => { + if (i === index) { + return { + ...tr, + description: val, + } + } + + return tr; + })); + }; + + return ( +
    +
    + {trainings?.map(({ title, description, document: file}, index) => ( +
    +
    + onChangeName(e?.target?.value, index)} + /> + onChangeDesc(e?.target?.value, index)} + /> + onRemoveFile(index)} + action="https://api.bbuddy.expert/api/home/uploadfile" + method="POST" + headers={{ + authorization: `Bearer ${jwt}`, + 'X-User-Language': locale, + 'X-Referrer-Channel': 'site', + }} + onChange={(obj) => onChange(obj.file, index)} + > + {i18nText('addDiploma', locale)} + +
    + } + onClick={() => deleteTrainings(index)} + /> +
    + ))} +
    + + {i18nText('addNew', locale)} + +
    + ); +}; diff --git a/src/components/view/FilledButton.tsx b/src/components/view/FilledButton.tsx index 3b875b6..f74a21f 100644 --- a/src/components/view/FilledButton.tsx +++ b/src/components/view/FilledButton.tsx @@ -12,3 +12,9 @@ export const FilledYellowButton = (props: any) => ( {props.children} ); + +export const FilledSquareButton = (props: any) => ( + +); diff --git a/src/components/view/LinkButton.tsx b/src/components/view/LinkButton.tsx index 6e06b0a..0a63f92 100644 --- a/src/components/view/LinkButton.tsx +++ b/src/components/view/LinkButton.tsx @@ -2,7 +2,7 @@ import React from 'react'; import { Button } from 'antd'; export const LinkButton = (props: any) => ( - ); diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index f27812b..a12d82f 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -105,6 +105,7 @@ export default { signUp: 'Jetzt anmelden', noData: 'Keine Daten', notFound: 'Nicht gefunden', + skillsInfo: 'Fähigkeiten-Infos', trainings: 'Trainings', seminars: 'Seminare', courses: 'Kurse', @@ -112,7 +113,38 @@ export default { aboutCoach: 'Über Coach', education: 'Bildung', coaching: 'Coaching', - + experiences: 'Praktische Erfahrung', + payInfo: 'Zahlungsdaten', + sessionDuration: 'Sitzungsdauer', + experienceHours: 'Gesamtstunden praktischer Erfahrung', + topics: 'Themen', + selectTopic: 'Thema auswählen', + title: 'Titel', + description: 'Beschreibung', + sessionCost: 'Sitzungskosten in Euro', + yourTimezone: 'Deine Zeitzone', + workTime: 'Arbeitszeit', + startAt: 'Beginn um', + finishAt: 'Ende um', + addWorkingHours: 'Arbeitszeiten hinzufügen', + specialisation: 'Spezialisierung', + selectSpecialisation: 'Wählen Sie Ihre Spezialisierung, um fortzufahren', + fillWeeklySchedule: 'Trage Sachen in deinen Wochenplan ein', + beneficiaryName: 'Name des Empfängers', + bicOrSwift: 'BIC/Swift-Code', + association: 'Verband', + level: 'Stufe', + addDiploma: 'Zertifikat hinzufügen', + university: 'Institution', + sunday: 'So', + monday: 'Mo', + tuesday: 'Di', + wednesday: 'Mi', + thursday: 'Do', + friday: 'Fr', + saturday: 'Sa', + addNew: 'Neu hinzufügen', + mExperiences: 'Führungserfahrung', errors: { invalidEmail: 'Die E-Mail-Adresse ist ungültig', emptyEmail: 'Bitte geben Sie Ihre E-Mail ein', diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index 9857844..117a2b7 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -110,8 +110,41 @@ export default { courses: 'Courses', mba: 'MBA Information', aboutCoach: 'About Coach', + skillsInfo: 'Skills Info', education: 'Education', coaching: 'Coaching', + experiences: 'Practical experience', + payInfo: 'Payment Info', + sessionDuration: 'Session duration', + experienceHours: 'Total hours of practical experience', + topics: 'Topics', + selectTopic: 'Select Topic', + title: 'Title', + description: 'Description', + sessionCost: 'Session cost in euro', + yourTimezone: 'Your timezone', + workTime: 'Work time', + startAt: 'Start at', + finishAt: 'Finish at', + addWorkingHours: 'Add working hours', + specialisation: 'Specialisation', + selectSpecialisation: 'Select your specialisation to proceed', + fillWeeklySchedule: 'Fill up your weekly schedule', + beneficiaryName: 'Beneficiary Name', + bicOrSwift: 'BIC/Swift code', + association: 'Association', + level: 'Level', + addDiploma: 'Add Diploma', + university: 'Institution', + sunday: 'Su', + monday: 'Mo', + tuesday: 'Tu', + wednesday: 'We', + thursday: 'Th', + friday: 'Fr', + saturday: 'Sa', + addNew: 'Add New', + mExperiences: 'Managerial Experience', errors: { invalidEmail: 'The email address is not valid', emptyEmail: 'Please enter your E-mail', diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index f94d8a5..5f4a314 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -105,6 +105,7 @@ export default { signUp: 'Regístrate ahora', noData: 'Sin datos', notFound: 'No encontrado', + skillsInfo: 'Información', trainings: 'Formación', seminars: 'Seminarios', courses: 'Cursos', @@ -112,7 +113,38 @@ export default { aboutCoach: 'Sobre el coach', education: 'Educación', coaching: 'Coaching', - + experiences: 'Experiencia práctica', + payInfo: 'Información de pago', + sessionDuration: 'Duración de la sesión', + experienceHours: 'Total de horas de experiencia práctica', + topics: 'Temas', + selectTopic: 'Seleccione el tema', + title: 'Título', + description: 'Descripción', + sessionCost: 'Coste de la sesión en euros', + yourTimezone: 'Tu zona horaria', + workTime: 'Tiempo de trabajo', + startAt: 'Empieza a las', + finishAt: 'Termina a las', + addWorkingHours: 'Añadir horas de trabajo', + specialisation: 'Especialización', + selectSpecialisation: 'Selecciona tu especialización para continuar', + fillWeeklySchedule: 'Rellena tu agenda semanal', + beneficiaryName: 'Nombre del beneficiario', + bicOrSwift: 'Código BIC/Swift', + association: 'Asociación', + level: 'Nivel', + addDiploma: 'Añadir diploma', + university: 'Institución', + sunday: 'D', + monday: 'L', + tuesday: 'M', + wednesday: 'X', + thursday: 'J', + friday: 'V', + saturday: 'S', + addNew: 'Añadir nuevo', + mExperiences: 'Experiencia de dirección', errors: { invalidEmail: 'La dirección de correo electrónico no es válida', emptyEmail: 'Introduce tu correo electrónico', diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index 7978155..3502aef 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -105,6 +105,7 @@ export default { signUp: 'Inscrivez-vous maintenant', noData: 'Aucune donnée', notFound: 'Non trouvé', + skillsInfo: 'Infos sur les compétences', trainings: 'Formations', seminars: 'Séminaires', courses: 'Cours', @@ -112,7 +113,38 @@ export default { aboutCoach: 'À propos du coach', education: 'Éducation', coaching: 'Coaching', - + experiences: 'Expérience pratique', + payInfo: 'Infos sur le paiement', + sessionDuration: 'Durée de la session', + experienceHours: 'Heures totales d\'expérience pratique', + topics: 'Sujets', + selectTopic: 'Sélectionnez un sujet', + title: 'Titre', + description: 'Description', + sessionCost: 'Coût de la session en euros', + yourTimezone: 'Votre fuseau horaire', + workTime: 'Heures de travail', + startAt: 'Commencer à', + finishAt: 'Finir à', + addWorkingHours: 'Ajouter des heures de travail', + specialisation: 'Spécialisation', + selectSpecialisation: 'Sélectionnez votre spécialisation pour continuer', + fillWeeklySchedule: 'Remplissez votre emploi du temps hebdomadaire', + beneficiaryName: 'Nom du bénéficiaire', + bicOrSwift: 'Code BIC/Swift', + association: 'Association', + level: 'Niveau', + addDiploma: 'Ajouter un diplôme', + university: 'Institution', + sunday: 'Di', + monday: 'Lu', + tuesday: 'Ma', + wednesday: 'Me', + thursday: 'Je', + friday: 'Ve', + saturday: 'Sa', + addNew: 'Ajouter un nouveau', + mExperiences: 'Expérience en gestion', errors: { invalidEmail: 'L\'adresse e-mail n\'est pas valide', emptyEmail: 'Veuillez saisir votre e-mail', diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 5520c2a..8a3743a 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -105,6 +105,7 @@ export default { signUp: 'Iscriviti ora', noData: 'Nessun dato', notFound: 'Non trovato', + skillsInfo: 'Info su competenze', trainings: 'Training', seminars: 'Seminari', courses: 'Corsi', @@ -112,7 +113,38 @@ export default { aboutCoach: 'Informazioni sul coach', education: 'Istruzione', coaching: 'Coaching', - + experiences: 'Esperienza pratica', + payInfo: 'Info pagamento', + sessionDuration: 'Durata della sessione', + experienceHours: 'Totale ore di esperienza pratica', + topics: 'Argomenti', + selectTopic: 'Seleziona l\'argomento', + title: 'Titolo', + description: 'Descrizione', + sessionCost: 'Costo della sessione in euro', + yourTimezone: 'Il tuo fuso orario', + workTime: 'Orario di lavoro', + startAt: 'Inizia alle', + finishAt: 'Termina alle', + addWorkingHours: 'Aggiungi ore lavorative', + specialisation: 'Specializzazione', + selectSpecialisation: 'Seleziona la tua specializzazione per continuare', + fillWeeklySchedule: 'Compila la tua agenda settimanale', + beneficiaryName: 'Nome del beneficiario', + bicOrSwift: 'BIC/codice Swift', + association: 'Associazione', + level: 'Livello', + addDiploma: 'Aggiungi diploma', + university: 'Istituto', + sunday: 'Do', + monday: 'Lu', + tuesday: 'Ma', + wednesday: 'Me', + thursday: 'Gi', + friday: 'Ve', + saturday: 'Sa', + addNew: 'Aggiungi nuovo', + mExperiences: 'Esperienza manageriale', errors: { invalidEmail: 'L\'indirizzo e-mail non è valido', emptyEmail: 'Inserisci l\'e-mail', diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index aa99952..de2a14b 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -98,21 +98,53 @@ export default { expertBackground: 'Профессиональный опыт эксперта', profCertification: 'Профессиональная сертификация', practiceHours: 'Часов практики', - supervisionCount: 'Часов супервизии в год', + supervisionCount: 'Супервизий в год', outOf: 'из', schedule: 'Расписание', successfulCase: 'Успешные случаи из практики', signUp: 'Записаться сейчас', noData: 'Нет данных', notFound: 'Не найдено', + skillsInfo: 'Навыки', trainings: 'Тренинги', seminars: 'Семинары', courses: 'Курсы', mba: 'Информация о MBA', + experiences: 'Практический опыт', aboutCoach: 'О коуче', education: 'Образование', coaching: 'Коучинг', - + payInfo: 'Платежная информация', + sessionDuration: 'Продолжительность сессии', + experienceHours: 'Общее количество часов практического опыта', + topics: 'Темы', + selectTopic: 'Выберите тему', + title: 'Название', + description: 'Описание', + sessionCost: 'Стоимость сессии в евро', + yourTimezone: 'Ваш часовой пояс', + workTime: 'Рабочее время', + startAt: 'Начало в', + finishAt: 'Завершение в', + addWorkingHours: 'Добавить рабочие часы', + specialisation: 'Специализация', + selectSpecialisation: 'Выберите свою специализацию для продолжения', + fillWeeklySchedule: 'Заполните свое недельное расписание', + beneficiaryName: 'Имя получателя', + bicOrSwift: 'BIC/Swift код', + association: 'Ассоциация', + level: 'Уровень', + addDiploma: 'Добавить диплом', + university: 'ВУЗ', + sunday: 'Вс', + monday: 'Пн', + tuesday: 'Вт', + wednesday: 'Ср', + thursday: 'Чт', + friday: 'Пт', + saturday: 'Сб', + addNew: 'Добавить', + mExperiences: 'Управленческий опыт', errors: { invalidEmail: 'Адрес электронной почты недействителен', emptyEmail: 'Пожалуйста, введите ваш E-mail', diff --git a/src/styles/_edu.scss b/src/styles/_edu.scss new file mode 100644 index 0000000..b3e65a0 --- /dev/null +++ b/src/styles/_edu.scss @@ -0,0 +1,35 @@ +.b-edu { + &-content { + display: flex; + flex-direction: column; + gap: 16px; + } + + &-list { + display: flex; + flex-direction: column; + gap: 12px; + width: 100%; + + &__item { + padding-top: 12px; + border-top: 1px solid #C4DFE6; + display: flex; + gap: 8px; + justify-content: space-between; + align-items: flex-start; + + &:first-child { + padding-top: 0; + border-top: none; + } + + & > div { + flex: 1; + display: flex; + flex-direction: column; + gap: 12px; + } + } + } +} diff --git a/src/styles/_modal.scss b/src/styles/_modal.scss index bf5423e..05be32d 100644 --- a/src/styles/_modal.scss +++ b/src/styles/_modal.scss @@ -51,7 +51,8 @@ } &__inner { - height: 60vh; + height: auto; + max-height: 60vh; overflow-y: auto; & > div { @@ -86,3 +87,13 @@ .ant-modal-mask { background-color: rgba(0, 59, 70, 0.4) !important; } + +.ant-upload-list-item-name { + max-width: 280px; +} + +.ant-upload-list-item { + &:hover { + background-color: transparent !important; + } +} diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index 49ceb51..394a403 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -1188,7 +1188,6 @@ height: 86px; border-radius: 16px; border: 2px solid #FFF; - background: lightgray 50%; box-shadow: 0 8px 16px 0 rgba(102, 165, 173, 0.32); overflow: hidden; @@ -1210,6 +1209,7 @@ @include rem(18); font-weight: 600; line-height: 150%; + margin-bottom: 16px; } } @@ -1233,6 +1233,61 @@ } } + &__info { + display: flex; + flex-direction: column; + gap: 8px; + + .title-h3 { + color: #003B46; + @include rem(16); + line-height: 18px; + } + + .case-list { + margin-top: 8px; + + p { + margin-top: 8px; + } + } + } + + &__practice { + color: #2C7873; + @include rem(16); + line-height: 18px; + } + + &__lang { + display: flex; + flex-direction: column; + gap: 8px; + + & > div { + color: #003B46; + @include rem(16); + line-height: 18px; + } + } + + &__list { + display: flex; + flex-direction: column; + gap: 8px; + + & > div { + display: flex; + gap: 12px; + } + } + + &__item { + color: #003B46; + @include rem(16); + line-height: 18px; + } + .title-h2 { color: #003B46; @include rem(18); @@ -1243,7 +1298,7 @@ margin-bottom: 0; } - &__desc { + &__desc, &__desc > div { border-radius: 16px; background: #EFFCFF; padding: 16px; @@ -1443,8 +1498,17 @@ top: 50%; transform: translateY(-50%); } - } } - +} + +.pay-data-list { + display: flex; + flex-direction: column; + gap: 8px; + + & > div { + display: flex; + gap: 16px; + } } diff --git a/src/styles/_schedule.scss b/src/styles/_schedule.scss new file mode 100644 index 0000000..4a3b9ec --- /dev/null +++ b/src/styles/_schedule.scss @@ -0,0 +1,15 @@ +.b-schedule-list { + display: flex; + flex-direction: column; + gap: 12px; + color: #003B46; + @include rem(16); + font-style: normal; + font-weight: 500; + line-height: 150%; + + & > div { + display: flex; + gap: 8px; + } +} diff --git a/src/styles/style.scss b/src/styles/style.scss index d00dbd2..cd8ed3f 100644 --- a/src/styles/style.scss +++ b/src/styles/style.scss @@ -15,6 +15,8 @@ @import "_message.scss"; @import "_auth-modal.scss"; @import "_modal.scss"; +@import "_edu.scss"; +@import "_schedule.scss"; @import "./view/style.scss"; @import "./sessions/style.scss"; diff --git a/src/styles/view/_buttons.scss b/src/styles/view/_buttons.scss index dfd9b8a..e5fc938 100644 --- a/src/styles/view/_buttons.scss +++ b/src/styles/view/_buttons.scss @@ -17,6 +17,19 @@ padding: 4px 24px !important; } + &_square { + width: 42px !important; + height: 42px !important; + background: #66A5AD !important; + font-size: 15px !important; + border-radius: 8px !important; + box-shadow: 0px 2px 4px 0px rgba(102, 165, 173, 0.32) !important; + position: absolute !important; + right: -8px !important; + bottom: -8px !important; + cursor: pointer; + } + &.danger { background: #D93E5C !important; box-shadow: none !important; @@ -28,6 +41,10 @@ font-size: 15px !important; height: auto !important; padding: 0 !important; + + &.danger { + color: #D93E5C !important; + } } &__outlined { diff --git a/src/styles/view/_collapse.scss b/src/styles/view/_collapse.scss new file mode 100644 index 0000000..491075c --- /dev/null +++ b/src/styles/view/_collapse.scss @@ -0,0 +1,15 @@ +.ant-collapse-header { + padding: 12px 0 !important; +} + +.ant-collapse-header-text { + color: #003B46; + @include rem(16); + font-style: normal; + font-weight: 600; + line-height: 133.333%; +} + +.ant-collapse-content-box { + padding: 12px 0 !important; +} diff --git a/src/styles/view/_input.scss b/src/styles/view/_input.scss index 3f6149d..392cbbf 100644 --- a/src/styles/view/_input.scss +++ b/src/styles/view/_input.scss @@ -8,6 +8,14 @@ input { background-color: transparent !important; + border-color: transparent !important; + box-shadow: none !important; + } + + .ant-input-group-addon { + background-color: transparent !important; + border-color: transparent !important; + box-shadow: none !important; } &:focus, &:hover, &:focus-within { diff --git a/src/styles/view/_practice.scss b/src/styles/view/_practice.scss new file mode 100644 index 0000000..921d855 --- /dev/null +++ b/src/styles/view/_practice.scss @@ -0,0 +1,29 @@ +.b-practice { + &-cases { + display: flex; + flex-direction: column; + gap: 16px; + } + + &-case { + &__header { + display: flex; + justify-content: space-between; + align-items: center; + } + + &__item { + display: flex; + justify-content: space-between; + gap: 8px; + align-items: flex-start; + } + + &__content { + display: flex; + flex-direction: column; + gap: 16px; + flex: 1; + } + } +} diff --git a/src/styles/view/style.scss b/src/styles/view/style.scss index 27a5921..42b0936 100644 --- a/src/styles/view/style.scss +++ b/src/styles/view/style.scss @@ -6,3 +6,5 @@ @import "_spin.scss"; @import "_switch.scss"; @import "_buttons.scss"; +@import "_practice.scss"; +@import "_collapse.scss"; diff --git a/src/types/education.ts b/src/types/education.ts index aa41a70..7cf99f7 100644 --- a/src/types/education.ts +++ b/src/types/education.ts @@ -1,22 +1,23 @@ import { ExpertDocument } from './file'; export type Details = { - id: number; - userId?:number; + id?: number; + userId?: number; title?: string; description?: string; - document?: ExpertDocument; + document?: ExpertDocument | null; }; export type Certificate = { - id: number; + id?: number; userId?: number; associationLevelId?: number; - document?: ExpertDocument; + associationId?: number; + document?: ExpertDocument | null; }; export type Experience = { - id: number, + id?: number, userId?: number, title?: string, description?: string @@ -24,10 +25,10 @@ export type Experience = { export type Association = { id: number; - name?: string; + name: string; }; -export type AssociationLevel = Association & { associationId?: number }; +export type AssociationLevel = Association & { associationId: number }; export type EducationData = { certificates?: Certificate[], diff --git a/src/types/practice.ts b/src/types/practice.ts index 438e125..1172a99 100644 --- a/src/types/practice.ts +++ b/src/types/practice.ts @@ -6,7 +6,7 @@ export type Supervision = { }; export type PracticeCase = { - id: number, + id?: number, userId?: number, description?: string, themesGroupIds?: number[] @@ -18,12 +18,14 @@ export type PracticeData = { sessionDuration?: number, sessionCost?: number, practiceCases?: PracticeCase[] -} +}; + +export type PracticePersonData = PracticeData & { + themesGroups?: ExpertsThemesGroups[], + supervisionPerYears?: Supervision[], + sessionCosts?: number[] +}; export interface PracticeDTO { - person4Data: PracticeData & { - themesGroups?: ExpertsThemesGroups[], - supervisionPerYears?: Supervision[], - sessionCosts?: number[] - } + person4Data: PracticePersonData } diff --git a/src/types/profile.ts b/src/types/profile.ts index 548398c..6da1047 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -15,7 +15,8 @@ export type ProfileData = { hasExternalLogin?: boolean; isTestMode?: boolean; phone?: string; - languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[] + languagesLinks?: { language: { id: number, code: string, nativeSpelling: string }, languageId: number }[]; + allLanguages?: { id: number, code: string, nativeSpelling: string }[] } export type Profile = ProfileData & { id: number }; diff --git a/src/utils/account.ts b/src/utils/account.ts index e67e439..ca29021 100644 --- a/src/utils/account.ts +++ b/src/utils/account.ts @@ -28,3 +28,19 @@ export const validateImage = (file: UploadFile, showMessage?: boolean): boolean return isImage && isLt5M; }; + + +export const validateDoc = (file: UploadFile): boolean => { + const isDoc = file.type === 'image/jpg' || file.type === 'image/jpeg' + || file.type === 'image/png' || file.type === 'image/gif' || file.type === 'application/pdf'; + if (!isDoc) { + message.error('You can only upload JPG/PNG/PDF file'); + } + + const isLt5M = file.size / 1024 / 1024 <= 5; + if (!isLt5M) { + message.error('Image must smaller than 5MB'); + } + + return isDoc && isLt5M; +}; diff --git a/src/utils/time.ts b/src/utils/time.ts new file mode 100644 index 0000000..9657e3a --- /dev/null +++ b/src/utils/time.ts @@ -0,0 +1,62 @@ +import dayjs from 'dayjs'; +import { WorkingTime } from '../types/schedule'; + +const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; +const MAX_DAY_TIME = 24 * 60; // min + +export const getCurrentTime = (data: WorkingTime) => { + let startDay = data.startDayOfWeekUtc; + let endDay = data.endDayOfWeekUtc; + const currentTimeZone = dayjs().format('Z'); + const startUtc = data.startTimeUtc / (1000 * 1000 * 60); + const endUtc = data.endTimeUtc / (1000 * 1000 * 60); + const matches = currentTimeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/); + const sign = matches[1]; + const h = matches[2]; + const m = matches[3]; + let startMin; + let endMin; + + if (sign === '+') { + startMin = startUtc + (Number(h) * 60) + Number(m); + endMin = endUtc + (Number(h) * 60) + Number(m); + // startMin = startUtc; + // endMin = endUtc; + } + + if (sign === '-') { + startMin = startUtc - (Number(h) * 60) - Number(m); + endMin = endUtc - (Number(h) * 60) - Number(m); + } + + if (startMin > MAX_DAY_TIME) { + startMin = startMin - MAX_DAY_TIME; + const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0; + startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + } + + if (endMin > MAX_DAY_TIME) { + endMin = endMin - MAX_DAY_TIME; + const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0; + endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + } + + if (startMin <= 0) { + startMin = MAX_DAY_TIME - startMin; + const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0; + startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + } + + if (endMin <= 0) { + endMin = MAX_DAY_TIME - endMin; + const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0; + endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + } + + return { + startDay, + startTime: `${(startMin - startMin % 60)/60}:${startMin % 60 || '00'}`, + endDay: endDay, + endTime: `${(endMin - endMin % 60)/60}:${endMin % 60 || '00'}` + } +}; From 74d93541a3d5a7d94290405b772885eaffbcae05 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Sat, 17 Aug 2024 23:26:12 +0400 Subject: [PATCH 12/23] blog pagination & sitemap --- .env | 6 +- src/app/[locale]/blog/[slug]/page.tsx | 19 +-- .../[locale]/blog/category/[slug]/page.tsx | 80 +------------ src/app/[locale]/blog/page.tsx | 110 +++++------------- src/app/sitemap.jsx | 27 +++++ .../BlogPosts/BlogPostCategories.tsx | 35 ++++++ src/components/BlogPosts/BlogPosts.tsx | 53 +++++++++ src/components/BlogPosts/BlogPostsList.tsx | 79 +++++++++++++ src/lib/contentful/blogPosts.ts | 20 +++- src/lib/contentful/contentfulClient.ts | 10 +- 10 files changed, 264 insertions(+), 175 deletions(-) create mode 100644 src/app/sitemap.jsx create mode 100644 src/components/BlogPosts/BlogPostCategories.tsx create mode 100644 src/components/BlogPosts/BlogPosts.tsx create mode 100644 src/components/BlogPosts/BlogPostsList.tsx diff --git a/.env b/.env index 509d982..2d0cc9f 100644 --- a/.env +++ b/.env @@ -1,6 +1,6 @@ NEXT_PUBLIC_SERVER_BASE_URL=https://api.bbuddy.expert/api NEXT_PUBLIC_AGORA_APPID=ed90c9dc42634e5687d4e2e0766b363f -CONTENTFUL_SPACE_ID = voxpxjq7y7vf -CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc -CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE \ No newline at end of file +NEXT_PUBLIC_CONTENTFUL_SPACE_ID = voxpxjq7y7vf +NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN = s99GWKfpDKkNwiEJ3pN7US_tmqsGvDlaex-sOJwpzuc +NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN = Z9WOKpLDbKNj7xVOmT_VXYNLH0AZwISFvQsq0PQlHfE diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index d1573dc..fe5f562 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -6,11 +6,6 @@ import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/ import Util from "node:util"; import RichText from "../../../../lib/contentful/RichText"; -export const metadata: Metadata = { - title: 'Bbuddy - Blog item', - description: 'Bbuddy desc blog item' -}; - interface BlogPostPageParams { slug: string } @@ -19,11 +14,19 @@ interface BlogPostPageProps { params: BlogPostPageParams } -export async function generateStaticParams(): Promise { - const blogPosts = await fetchBlogPosts({ preview: false }) +export async function generateMetadata({ params }: BlogPostPageProps, parent: ResolvingMetadata): Promise { + const blogPost = await fetchBlogPost({ slug: params.slug, preview: draftMode().isEnabled }) - return blogPosts.map((post) => ({ slug: post.slug })) + if (!blogPost) { + return notFound() + } + + return { + title: blogPost.title + } } + + function renderWidget (widget: Widget) { switch (widget.type){ case 'widgetParagraph': diff --git a/src/app/[locale]/blog/category/[slug]/page.tsx b/src/app/[locale]/blog/category/[slug]/page.tsx index 696247c..2c897ed 100644 --- a/src/app/[locale]/blog/category/[slug]/page.tsx +++ b/src/app/[locale]/blog/category/[slug]/page.tsx @@ -5,6 +5,7 @@ import {unstable_setRequestLocale} from "next-intl/server"; import Link from "next/link"; import {fetchBlogPosts} from "../../../../../lib/contentful/blogPosts"; import {fetchBlogPostCategories} from "../../../../../lib/contentful/blogPostsCategories"; +import {BlogPosts} from "../../../../../components/BlogPosts/BlogPosts"; export const metadata: Metadata = { title: 'Bbuddy - Blog', @@ -19,83 +20,10 @@ interface BlogPostPageProps { params: BlogPostPageParams } -export default async function Blog({params}: { params: BlogPostPageParams }) { +export default async function Blog({params, searchParams}: { params: BlogPostPageParams, searhParams?: {page: number} }) { unstable_setRequestLocale(params.locale); - const data = await fetchBlogPosts({ preview: draftMode().isEnabled, locale: params.locale, category: params.slug }) - const cats = await fetchBlogPostCategories(false) + const page = searchParams.page || undefined return ( -
    -
    -
    -

    - Mentorship, Career
    - Development & Coaching. -

    -
    -

    The ins-and-outs of building a career in tech, gaining
    experience

    -

    from a mentor, and getting your feet wet with coaching.

    -
    -
    - -
    -
    -
    -
    -
    -
    - { - cats.map((cat, i)=>( - {cat.title} - )) - } -
    -
    -
    -
    -
    -
    - {data.map((item, i) => ( -
  15. - -
    - {item.listImage?.alt}/ -
    -
    -
    -
    - {item.title} -
    -
    {item.category}
    -
    - {item.excerpt} -
    -
    -
    -
    - -
    -
    {item.author.name}
    -
    {item.createdAt}
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    - -
  16. - ))} -
    -
    -
    -
    + ); } diff --git a/src/app/[locale]/blog/page.tsx b/src/app/[locale]/blog/page.tsx index 3ce264d..f23d5f1 100644 --- a/src/app/[locale]/blog/page.tsx +++ b/src/app/[locale]/blog/page.tsx @@ -5,91 +5,39 @@ import {fetchBlogPosts} from "../../../lib/contentful/blogPosts"; import {unstable_setRequestLocale} from "next-intl/server"; import Link from "next/link"; import {fetchBlogPostCategories} from "../../../lib/contentful/blogPostsCategories"; - -export const metadata: Metadata = { - title: 'Bbuddy - Blog', - description: 'Bbuddy desc blog' -}; +import {CustomPagination} from "../../../components/view/CustomPagination"; +import {DEFAULT_PAGE_SIZE} from "../../../constants/common"; +import {BlogPosts} from "../../../components/BlogPosts/BlogPosts"; +interface BlogPostPageParams { + slug: string +} -export default async function Blog({ params: { locale } }: { params: { locale: string } }) { +interface BlogPostPageProps { + params: BlogPostPageParams +} + +export async function generateStaticParams(): Promise { + const blogPosts = await fetchBlogPosts({ preview: false }) + + return blogPosts.data.map((post) => ({ slug: post.slug })) +} + + +export default async function Blog({ params: { locale }, searchParams }: { params: { locale: string }, searhParams?: {page: number} }) { unstable_setRequestLocale(locale); - const data = await fetchBlogPosts(false, locale) - const cats = await fetchBlogPostCategories(false) + const pageSize = DEFAULT_PAGE_SIZE + const page = searchParams.page || undefined + // BlogPosts('/'+locale+'/blog/', locale, pageSize) return ( -
    -
    -
    -

    - Mentorship, Career
    - Development & Coaching. -

    -
    -

    The ins-and-outs of building a career in tech, gaining
    experience

    -

    from a mentor, and getting your feet wet with coaching.

    -
    -
    - -
    -
    -
    -
    -
    -
    - { - cats.map((cat, i)=>( - {cat.title} - )) - } -
    -
    -
    -
    -
    -
    - {data.map((item, i) => ( -
  17. - -
    - {item.listImage?.alt}/ -
    -
    -
    -
    - {item.title} -
    -
    {item.category}
    -
    - {item.excerpt} -
    -
    -
    -
    - -
    -
    {item.author.name}
    -
    {item.createdAt}
    -
    -
    -
    -
    - - 165 -
    -
    - - Share -
    -
    -
    -
    - -
  18. - ))} -
    -
    -
    -
    + + + ); } diff --git a/src/app/sitemap.jsx b/src/app/sitemap.jsx new file mode 100644 index 0000000..84e710a --- /dev/null +++ b/src/app/sitemap.jsx @@ -0,0 +1,27 @@ +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 +} \ No newline at end of file diff --git a/src/components/BlogPosts/BlogPostCategories.tsx b/src/components/BlogPosts/BlogPostCategories.tsx new file mode 100644 index 0000000..3f31eb3 --- /dev/null +++ b/src/components/BlogPosts/BlogPostCategories.tsx @@ -0,0 +1,35 @@ +'use client'; +import React, {useState} from "react"; +import {Languages} from "../../types/tags"; +import {BlogPostCategory} from "../../types/blogPostCategory"; +import Link from "next/link"; + +type Props = { + languages?: Languages; + basePath: string; + locale: string; + cats: BlogPostCategory[], + slug: string +}; + +export const BlogPostCategories = ({ basePath = '/', cats = [], slug = '' }: Props) => { + const [currentCat, setCurrentCat] = useState(slug); + return ( +
    +
    +
    + { + cats.map((cat, i)=>( + + {cat.title} + + )) + } +
    +
    +
    + ) +} diff --git a/src/components/BlogPosts/BlogPosts.tsx b/src/components/BlogPosts/BlogPosts.tsx new file mode 100644 index 0000000..0ec761c --- /dev/null +++ b/src/components/BlogPosts/BlogPosts.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { DEFAULT_PAGE_SIZE } from '../../constants/common'; +import {getLanguages} from "../../actions/tags"; +import {fetchBlogPosts} from "../../lib/contentful/blogPosts"; +import {fetchBlogPostCategories} from "../../lib/contentful/blogPostsCategories"; +import {BlogPostsList} from "./BlogPostsList"; +import {BlogPostCategories} from "./BlogPostCategories"; + +type PostsProps = { + basePath: string; + locale: string; + pageSize?: number; + currentCat: string; + page?: number +}; + +export const BlogPosts = async ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE, currentCat = '', page = 1 }: PostsProps) => { + const languages = await getLanguages(locale); + const {data, total} = await fetchBlogPosts({preview: false, category: currentCat, page: page}) + const cats = await fetchBlogPostCategories(false) + + return ( +
    +
    +
    +

    + Mentorship, Career
    + Development & Coaching +

    +
    +

    The ins-and-outs of building a career in tech, gaining
    experience

    +

    from a mentor, and getting your feet wet with coaching.

    +
    +
    + +
    +
    +
    + + +
    + ) +} \ No newline at end of file diff --git a/src/components/BlogPosts/BlogPostsList.tsx b/src/components/BlogPosts/BlogPostsList.tsx new file mode 100644 index 0000000..70f85da --- /dev/null +++ b/src/components/BlogPosts/BlogPostsList.tsx @@ -0,0 +1,79 @@ +'use client'; + +import React from 'react'; +import { DEFAULT_PAGE_SIZE } from '../../constants/common'; +import {Languages, SearchData} from "../../types/tags"; +import {BlogPost} from "../../types/blogPost"; +import Link from "next/link"; +import {CustomPagination} from "../view/CustomPagination"; + +type Props = { + searchData?: SearchData; + languages?: Languages; + basePath: string; + locale: string; + data: BlogPost[], + total: number +}; + +export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE, data = [], total= 0 }: Props) => { + const currentPage = 1 + const onChangePage = (page: number) => { + router.push(page === 1 ? basePath : basePath+'?page='+page); + }; + + return ( +
    +
    +
    + {data.map((item, i) => ( +
  19. + +
    + {item.listImage?.alt}/ +
    +
    +
    +
    + {item.title} +
    +
    {item.category}
    +
    + {item.excerpt} +
    +
    +
    +
    + +
    +
    {item.author.name}
    +
    {item.createdAt}
    +
    +
    +
    +
    + + 165 +
    +
    + + Share +
    +
    +
    +
    + +
  20. + ))} +
    + {total > pageSize && ( + )} +
    +
    + ) +} \ No newline at end of file diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts index 6fa939b..415792c 100644 --- a/src/lib/contentful/blogPosts.ts +++ b/src/lib/contentful/blogPosts.ts @@ -8,7 +8,9 @@ import {WidgetMedia, WidgetMediaEntry} from "../../types/blogWidgets/widgetMedia import {WidgetParagraph} from "../../types/blogWidgets/widgetParagraph"; import entry from "next/dist/server/typescript/rules/entry"; import Util from "node:util"; +import {DEFAULT_PAGE_SIZE} from "../../constants/common"; +const pageSize = DEFAULT_PAGE_SIZE type PostEntry = BlogPostEntry//Entry type widgetEnum = WidgetParagraph | WidgetMedia export type Widget = { @@ -65,8 +67,12 @@ interface FetchBlogPostsOptions { preview: boolean local?: string category?: string + page?: number } -export async function fetchBlogPosts({ preview, category }: FetchBlogPostsOptions): Promise { +export async function fetchBlogPosts({ preview, category, page }: FetchBlogPostsOptions): Promise<{ + total: number; + data: BlogPost[] +}> { const contentful = contentfulClient({ preview }) const query = { content_type: 'blogPost', @@ -77,9 +83,19 @@ export async function fetchBlogPosts({ preview, category }: FetchBlogPostsOption query['fields.category.fields.slug'] = category query['fields.category.sys.contentType.sys.id']='blogPostCategory' } + + if(page){ + query['limit'] = pageSize + query['skip'] = pageSize * (page - 1) + } + const blogPostsResult = await contentful.getEntries(query) - return blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) + const data = blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) + return { + total: blogPostsResult.total, + data + } } interface FetchBlogPostOptions { diff --git a/src/lib/contentful/contentfulClient.ts b/src/lib/contentful/contentfulClient.ts index 61fcb2e..c8abf51 100644 --- a/src/lib/contentful/contentfulClient.ts +++ b/src/lib/contentful/contentfulClient.ts @@ -1,19 +1,19 @@ import { createClient } from 'contentful' -const { CONTENTFUL_SPACE_ID, CONTENTFUL_ACCESS_TOKEN, CONTENTFUL_PREVIEW_ACCESS_TOKEN } = process.env +const { NEXT_PUBLIC_CONTENTFUL_SPACE_ID, NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN, NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN } = process.env // This is the standard Contentful client. It fetches // content that has been published. const client = createClient({ - space: CONTENTFUL_SPACE_ID!, - accessToken: CONTENTFUL_ACCESS_TOKEN!, + space: NEXT_PUBLIC_CONTENTFUL_SPACE_ID!, + accessToken: NEXT_PUBLIC_CONTENTFUL_ACCESS_TOKEN!, }) // This is a Contentful client that's been configured // to fetch drafts and unpublished content. const previewClient = createClient({ - space: CONTENTFUL_SPACE_ID!, - accessToken: CONTENTFUL_PREVIEW_ACCESS_TOKEN!, + space: NEXT_PUBLIC_CONTENTFUL_SPACE_ID!, + accessToken: NEXT_PUBLIC_CONTENTFUL_PREVIEW_ACCESS_TOKEN!, host: 'preview.contentful.com', }) From 44674a191002fe1d34b1d79beeca47c36e0e59fb Mon Sep 17 00:00:00 2001 From: norton81 Date: Tue, 20 Aug 2024 13:06:40 +0300 Subject: [PATCH 13/23] fix master --- .../account/(account)/expert-profile/add-offer/page.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx index e669b63..7ed015c 100644 --- a/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/add-offer/page.tsx @@ -1,6 +1,6 @@ import React from 'react'; -import { Link } from '../../../../../../../navigation'; -import { CustomSelect } from '../../../../../../../components/view/CustomSelect'; +import { Link } from '../../../../../../navigation'; +import { CustomSelect } from '../../../../../../components/view/CustomSelect'; export default function AddOffer() { return ( From 8ee52bc834c6eecda7bfd54cc0549f72ddc60121 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Thu, 22 Aug 2024 17:19:22 +0300 Subject: [PATCH 14/23] blog fix cat link --- src/app/[locale]/blog/[slug]/page.tsx | 8 ++++---- src/components/BlogPosts/BlogPostsList.tsx | 5 +++-- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index fe5f562..2f6aefd 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -27,20 +27,20 @@ export async function generateMetadata({ params }: BlogPostPageProps, parent: Re } -function renderWidget (widget: Widget) { +function renderWidget (widget: Widget, index: number) { switch (widget.type){ case 'widgetParagraph': return ( - <> +

    {widget.widget.subTitle}

    - +
    ) case 'widgetMedia': return ( - + ) } } diff --git a/src/components/BlogPosts/BlogPostsList.tsx b/src/components/BlogPosts/BlogPostsList.tsx index 70f85da..954402d 100644 --- a/src/components/BlogPosts/BlogPostsList.tsx +++ b/src/components/BlogPosts/BlogPostsList.tsx @@ -13,7 +13,8 @@ type Props = { basePath: string; locale: string; data: BlogPost[], - total: number + total: number, + pageSize: number }; export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE_SIZE, data = [], total= 0 }: Props) => { @@ -28,7 +29,7 @@ export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE
    {data.map((item, i) => (
  21. - +
    {item.listImage?.alt}/
    From f7fe427aae98d3a33a713851aa0f4d80cb1f8b83 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Thu, 22 Aug 2024 18:15:36 +0300 Subject: [PATCH 15/23] fix lock --- package-lock.json | 731 +++++++++++++++++++++++++++------------------- 1 file changed, 432 insertions(+), 299 deletions(-) diff --git a/package-lock.json b/package-lock.json index 71926a5..79eeae5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,11 +11,13 @@ "@ant-design/cssinjs": "^1.18.1", "@ant-design/icons": "^5.2.6", "@ant-design/nextjs-registry": "^1.0.0", + "@contentful/rich-text-react-renderer": "^15.22.9", "agora-rtc-react": "^2.1.0", "agora-rtc-sdk-ng": "^4.20.2", "antd": "^5.12.1", "antd-img-crop": "^4.21.0", "axios": "^1.6.5", + "contentful": "^10.13.3", "dayjs": "^1.11.10", "lodash": "^4.17.21", "next": "14.0.3", @@ -42,48 +44,47 @@ } }, "node_modules/@agora-js/media": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@agora-js/media/-/media-4.21.0.tgz", - "integrity": "sha512-X4aV84/gB4O7GOOkwP3+NGTHtT2IVkpa4DFBTlBNl7hrkjDwUeanzCQZyva92Zyw59N0NI6dKh9CjJKoIL5Now==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@agora-js/media/-/media-4.22.0.tgz", + "integrity": "sha512-6IYuzm6lUQ0xmkg70z+zc4GHSu+VdnuQvq8x12GnYKcKAUf13s3h6EMy68DLG4AjLU0c/bN2uNDt74u9Zwc7vQ==", "dependencies": { - "@agora-js/report": "4.21.0", - "@agora-js/shared": "4.21.0", + "@agora-js/report": "4.22.0", + "@agora-js/shared": "4.22.0", "agora-rte-extension": "^1.2.4", "axios": "^1.6.8", - "pako": "^2.1.0", "webrtc-adapter": "8.2.0" } }, "node_modules/@agora-js/report": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@agora-js/report/-/report-4.21.0.tgz", - "integrity": "sha512-c8KIdomuPItwvri431z5CPT7L4m+jLJrwWWt/QS3JN+sp/t49NnoOIyKQf3y5xCbyNPCNNeGofrwkaIRu4YE8g==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@agora-js/report/-/report-4.22.0.tgz", + "integrity": "sha512-6LfrvRw9O97R1FP00vdPfS4hCjA8WMEllN7JDxTBnfPDaS+XHgu+ewcTkpSnhFVQG2pM45lwuE0G9F0RKLF5Jw==", "dependencies": { - "@agora-js/shared": "4.21.0", + "@agora-js/shared": "4.22.0", "axios": "^1.6.8" } }, "node_modules/@agora-js/shared": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/@agora-js/shared/-/shared-4.21.0.tgz", - "integrity": "sha512-oqaiuIhG9ai/NXUDEmj9b3uGxxU9I0OZZszNaJexjakJuVZPM7ypzrCLUi5SzkTh++QOf68yuvD488fjq5WQsA==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/@agora-js/shared/-/shared-4.22.0.tgz", + "integrity": "sha512-taKwc0AqbwCHHJL/2VafRQ7thgOYC1c6tiRweL1X3QpfBjJdXYVjc9jn2zY9NAZO4l4+5f1S9t988d1536XPtQ==", "dependencies": { "axios": "^1.6.8", "ua-parser-js": "^0.7.34" } }, "node_modules/@ant-design/colors": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.0.2.tgz", - "integrity": "sha512-7KJkhTiPiLHSu+LmMJnehfJ6242OCxSlR3xHVBecYxnMW8MS/878NXct1GqYARyL59fyeFdKRxXTfvR9SnDgJg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@ant-design/colors/-/colors-7.1.0.tgz", + "integrity": "sha512-MMoDGWn1y9LdQJQSHiCC20x3uZ3CwQnv9QMz6pCmJOrqdgM9YxsoVVY0wtrdXbmfSgnV0KNk6zi09NAhMR2jvg==", "dependencies": { "@ctrl/tinycolor": "^3.6.1" } }, "node_modules/@ant-design/cssinjs": { - "version": "1.21.0", - "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.21.0.tgz", - "integrity": "sha512-gIilraPl+9EoKdYxnupxjHB/Q6IHNRjEXszKbDxZdsgv4sAZ9pjkCq8yanDWNvyfjp4leir2OVAJm0vxwKK8YA==", + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-1.21.1.tgz", + "integrity": "sha512-tyWnlK+XH7Bumd0byfbCiZNK43HEubMoCcu9VxwsAwiHdHTgWa+tMN0/yvxa+e8EzuFP1WdUNNPclRpVtD33lg==", "dependencies": { "@babel/runtime": "^7.11.1", "@emotion/hash": "^0.8.0", @@ -91,21 +92,46 @@ "classnames": "^2.3.1", "csstype": "^3.1.3", "rc-util": "^5.35.0", - "stylis": "^4.0.13" + "stylis": "^4.3.3" }, "peerDependencies": { "react": ">=16.0.0", "react-dom": ">=16.0.0" } }, + "node_modules/@ant-design/cssinjs-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@ant-design/cssinjs-utils/-/cssinjs-utils-1.0.3.tgz", + "integrity": "sha512-BrztZZKuoYcJK8uEH40ylBemf/Mu/QPiDos56g2bv6eUoniQkgQHOCOvA3+pncoFO1TaS8xcUCIqGzDA0I+ZVQ==", + "dependencies": { + "@ant-design/cssinjs": "^1.21.0", + "@babel/runtime": "^7.23.2", + "rc-util": "^5.38.0" + }, + "peerDependencies": { + "react": ">=16.9.0", + "react-dom": ">=16.9.0" + } + }, + "node_modules/@ant-design/fast-color": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@ant-design/fast-color/-/fast-color-2.0.6.tgz", + "integrity": "sha512-y2217gk4NqL35giHl72o6Zzqji9O7vHh9YmhUVkPtAOpoTCH4uWxo/pr4VE8t0+ChEPs0qo4eJRC5Q1eXWo3vA==", + "dependencies": { + "@babel/runtime": "^7.24.7" + }, + "engines": { + "node": ">=8.x" + } + }, "node_modules/@ant-design/icons": { - "version": "5.3.7", - "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.3.7.tgz", - "integrity": "sha512-bCPXTAg66f5bdccM4TT21SQBDO1Ek2gho9h3nO9DAKXJP4sq+5VBjrQMSxMVXSB3HyEz+cUbHQ5+6ogxCOpaew==", + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@ant-design/icons/-/icons-5.4.0.tgz", + "integrity": "sha512-QZbWC5xQYexCI5q4/fehSEkchJr5UGtvAJweT743qKUQQGs9IH2DehNLP49DJ3Ii9m9CijD2HN6fNy3WKhIFdA==", "dependencies": { "@ant-design/colors": "^7.0.0", "@ant-design/icons-svg": "^4.4.0", - "@babel/runtime": "^7.11.2", + "@babel/runtime": "^7.24.8", "classnames": "^2.2.6", "rc-util": "^5.31.1" }, @@ -123,9 +149,9 @@ "integrity": "sha512-vHbT+zJEVzllwP+CM+ul7reTEfBR0vgxFe7+lREAsAA7YGsYpboiq2sQNeQeRvh09GfQgs/GyFEvZpJ9cLXpXA==" }, "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==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@ant-design/nextjs-registry/-/nextjs-registry-1.0.1.tgz", + "integrity": "sha512-DaMJ1nClR1a4UfG7vXkDj89z1eARhSDgqvHoxfM0Yco1MZEbaqRj4o+bQToHb3gMb6gbFlrZ51nOBGh5xSJ7EQ==", "peerDependencies": { "@ant-design/cssinjs": "^1.18.2", "antd": "^5.0.0", @@ -150,9 +176,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", - "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", + "version": "7.25.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.4.tgz", + "integrity": "sha512-DSgLeL/FNcpXuzav5wfYvHCGvynXkJbn3Zvc3823AEe9nPwW9IK4UoCSS5yGymmQzN0pCPvivtgS6/8U2kkm1w==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -160,6 +186,38 @@ "node": ">=6.9.0" } }, + "node_modules/@contentful/content-source-maps": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@contentful/content-source-maps/-/content-source-maps-0.11.0.tgz", + "integrity": "sha512-eS/Bm1hzv5C3SyTpP08+sYVQ7fFcNUdANrLsotUWk+uC7WpponQIIY26R9QZhX7tE3r5Nq80Z+aR7uo5u31ksg==", + "dependencies": { + "@vercel/stega": "^0.1.2", + "json-pointer": "^0.6.2" + } + }, + "node_modules/@contentful/rich-text-react-renderer": { + "version": "15.22.9", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-react-renderer/-/rich-text-react-renderer-15.22.9.tgz", + "integrity": "sha512-ubzuQKaIwB7AeUi1zHQ6EMvpLOMJ9p5LGxZznkcHG1Sn91LNZorQXfMt9K4tSoOR9Cn8KxN6ca8gMNNh5zGbig==", + "dependencies": { + "@contentful/rich-text-types": "^16.8.3" + }, + "engines": { + "node": ">=6.0.0" + }, + "peerDependencies": { + "react": "^16.8.6 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.6 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/@contentful/rich-text-types": { + "version": "16.8.3", + "resolved": "https://registry.npmjs.org/@contentful/rich-text-types/-/rich-text-types-16.8.3.tgz", + "integrity": "sha512-vXwXDQMDbqITCWfTkU5R/q+uvXWCc1eYNvdZyjtrs0YDIYr4L7QJ2s1r4ZheIs3iVf3AFucKIHgDSpwCAm2wKA==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@ctrl/tinycolor": { "version": "3.6.1", "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-3.6.1.tgz", @@ -256,14 +314,6 @@ "tslib": "^2.4.0" } }, - "node_modules/@formatjs/ecma402-abstract/node_modules/@formatjs/intl-localematcher": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", - "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", - "dependencies": { - "tslib": "^2.4.0" - } - }, "node_modules/@formatjs/fast-memoize": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-2.2.0.tgz", @@ -292,9 +342,9 @@ } }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.2.32", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.2.32.tgz", - "integrity": "sha512-k/MEBstff4sttohyEpXxCmC3MqbUn9VvHGlZ8fauLzkbwXmVrEeyzS+4uhrvAk9DWU9/7otYWxyDox4nT/KVLQ==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.5.4.tgz", + "integrity": "sha512-zTwEpWOzZ2CiKcB93BLngUX59hQkuZjT2+SAQEscSm52peDW/getsawMcWF1rGRpMCX6D7nSJA3CzJ8gn13N/g==", "dependencies": { "tslib": "^2.4.0" } @@ -384,9 +434,9 @@ "integrity": "sha512-7xRqh9nMvP5xrW4/+L0jgRRX+HoNRGnfJpD+5Wq6/13j3dsdzxO3BCXn7D3hMqsDb+vjZnJq+vI7+EtgrYZTeA==" }, "node_modules/@next/eslint-plugin-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.4.tgz", - "integrity": "sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-14.2.6.tgz", + "integrity": "sha512-d3+p4AjIYmhqzYHhhmkRYYN6ZU35TwZAKX08xKRfnHkz72KhWL2kxMFsDptpZs5e8bBGdepn7vn1+9DaF8iX+A==", "dev": true, "dependencies": { "glob": "10.3.10" @@ -584,12 +634,12 @@ } }, "node_modules/@rc-component/color-picker": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-1.5.3.tgz", - "integrity": "sha512-+tGGH3nLmYXTalVe0L8hSZNs73VTP5ueSHwUlDC77KKRaN7G4DS4wcpG5DTDzdcV/Yas+rzA6UGgIyzd8fS4cw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@rc-component/color-picker/-/color-picker-2.0.1.tgz", + "integrity": "sha512-WcZYwAThV/b2GISQ8F+7650r5ZZJ043E57aVBFkQ+kSY4C6wdofXgB0hBx+GPGpIU0Z81eETNoDUJMr7oy/P8Q==", "dependencies": { + "@ant-design/fast-color": "^2.0.6", "@babel/runtime": "^7.23.6", - "@ctrl/tinycolor": "^3.6.1", "classnames": "^2.2.6", "rc-util": "^5.38.1" }, @@ -693,9 +743,9 @@ } }, "node_modules/@rc-component/trigger": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.0.tgz", - "integrity": "sha512-QarBCji02YE9aRFhZgRZmOpXBj0IZutRippsVBv85sxvG4FGk/vRxwAlkn3MS9zK5mwbETd86mAVg2tKqTkdJA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@rc-component/trigger/-/trigger-2.2.1.tgz", + "integrity": "sha512-fuU11J8pOt6+U/tU6/CAv8wjCwGaNeRk9f5k8HQth7JBbJ6MMH62WhGycVW75VnXfBZgL/7kO+wbiO2Xc9U9sQ==", "dependencies": { "@babel/runtime": "^7.23.2", "@rc-component/portal": "^1.1.0", @@ -713,9 +763,9 @@ } }, "node_modules/@rushstack/eslint-patch": { - "version": "1.10.3", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.3.tgz", - "integrity": "sha512-qC/xYId4NMebE6w/V33Fh9gWxLgURiNYgVNObbJl2LZv0GUUItCcCqC5axQSwRaAgaxl2mELq1rMzlswaQ0Zxg==", + "version": "1.10.4", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.4.tgz", + "integrity": "sha512-WJgX9nzTqknM393q1QJDJmoW28kUfEnybeTfVNcNAPnIx210RXm2DiXiHzfNPJNIUUb1tJnz/l4QGtJ30PgWmA==", "dev": true }, "node_modules/@swc/helpers": { @@ -733,18 +783,18 @@ "dev": true }, "node_modules/@types/lodash": { - "version": "4.17.6", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.6.tgz", - "integrity": "sha512-OpXEVoCKSS3lQqjx9GGGOapBeuW5eUboYHRlHP9urXPX25IKZ6AnP5ZRxtVf63iieUbsHxLn8NQ5Nlftc6yzAA==", + "version": "4.17.7", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.7.tgz", + "integrity": "sha512-8wTvZawATi/lsmNu10/j2hk1KEP0IvjubqPE3cu1Xz7xfXXt5oCq3SNUz4fMIP4XGF9Ky+Ue2tBA3hcS7LSBlA==", "dev": true }, "node_modules/@types/node": { - "version": "20.14.9", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.9.tgz", - "integrity": "sha512-06OCtnTXtWOZBJlRApleWndH4JsRVs1pDCc8dLSQp+7PpUpX3ePdHyeNSFTeSe7FtKyQkrlPvHwJOW3SLd8Oyg==", + "version": "20.16.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.16.1.tgz", + "integrity": "sha512-zJDo7wEadFtSyNz5QITDfRcrhqDvQI1xQNQ0VoizPjM/dVAODqqIUWbJPkvsxmTI0MYRGRikcdjMPhOssnPejQ==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/prop-types": { @@ -754,9 +804,9 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.3.3", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", - "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", + "version": "18.3.4", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.4.tgz", + "integrity": "sha512-J7W30FTdfCxDDjmfRM+/JqLHBIyl7xUIp9kwK637FGmY7+mkSFSe6L4jpZzhj5QMfLssSDP4/i75AKkrdC7/Jw==", "dev": true, "dependencies": { "@types/prop-types": "*", @@ -919,10 +969,15 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", "dev": true }, + "node_modules/@vercel/stega": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@vercel/stega/-/stega-0.1.2.tgz", + "integrity": "sha512-P7mafQXjkrsoyTRppnt0N21udKS9wUmLXHRyP9saLXLHw32j/FgUJ3FscSWgvSqRs4cj7wKZtwqJEvWJ2jbGmA==" + }, "node_modules/acorn": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz", - "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==", + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz", + "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==", "dev": true, "bin": { "acorn": "bin/acorn" @@ -941,9 +996,9 @@ } }, "node_modules/agora-rtc-react": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/agora-rtc-react/-/agora-rtc-react-2.2.0.tgz", - "integrity": "sha512-AELqEjvZiWNvSHc/uS5Cm31dvcrtE3oLNcLuSRsYayokBke9fx1GhsFIvVo+elXIuLXCDm0XjhTLoF/gz780xw==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/agora-rtc-react/-/agora-rtc-react-2.3.0.tgz", + "integrity": "sha512-6D0uvXoZFlwQ/DClceJ1PUCpaHv3ebfMKFOnU0DXbiLpeMeYWM2uyuvfrcDjg4fGf033wPEzXVJHS0wx/miyJw==", "engines": { "node": ">=10" }, @@ -952,16 +1007,17 @@ } }, "node_modules/agora-rtc-sdk-ng": { - "version": "4.21.0", - "resolved": "https://registry.npmjs.org/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.21.0.tgz", - "integrity": "sha512-EAZMdhbqIXl/PtFqEQn0O5Pmj3Tt+4oTXtd76MK1CozgbcDsc0TmFZK3qM25eaKqhzTz1wiVCwzBCWs3pF5OpQ==", + "version": "4.22.0", + "resolved": "https://registry.npmjs.org/agora-rtc-sdk-ng/-/agora-rtc-sdk-ng-4.22.0.tgz", + "integrity": "sha512-mP6BDNP6oV01IQV7yXZ4wIuwDpoHaK6ARiDVKDaD+fK3LXXWqUtCweZLwzTde+OYkauPEsivqNbkAp/q6Ggqtg==", "dependencies": { - "@agora-js/media": "4.21.0", - "@agora-js/report": "4.21.0", - "@agora-js/shared": "4.21.0", + "@agora-js/media": "4.22.0", + "@agora-js/report": "4.22.0", + "@agora-js/shared": "4.22.0", "agora-rte-extension": "^1.2.4", "axios": "^1.6.8", "formdata-polyfill": "^4.0.7", + "pako": "^2.1.0", "ua-parser-js": "^0.7.34", "webrtc-adapter": "8.2.0" } @@ -1012,21 +1068,22 @@ } }, "node_modules/antd": { - "version": "5.19.0", - "resolved": "https://registry.npmjs.org/antd/-/antd-5.19.0.tgz", - "integrity": "sha512-+w+3zJUKqwU1GSXUxnKhzSGuWUqwwpHQm/voJr2X0JBdYxr9gkLhKR0HBhQjVSSJzSb86rB48fUbByHKrN05Xg==", + "version": "5.20.2", + "resolved": "https://registry.npmjs.org/antd/-/antd-5.20.2.tgz", + "integrity": "sha512-9d6Bs5ZKIV+JhB0eD7KxYnIfnhUh86kNtTGIuNiIxHFUhbuyT1DXN2SuMksDmtSfuRYZ82/C4hq+OJjWNNbmHg==", "dependencies": { - "@ant-design/colors": "^7.0.2", + "@ant-design/colors": "^7.1.0", "@ant-design/cssinjs": "^1.21.0", - "@ant-design/icons": "^5.3.7", + "@ant-design/cssinjs-utils": "^1.0.3", + "@ant-design/icons": "^5.4.0", "@ant-design/react-slick": "~1.1.2", - "@babel/runtime": "^7.24.7", + "@babel/runtime": "^7.24.8", "@ctrl/tinycolor": "^3.6.1", - "@rc-component/color-picker": "~1.5.3", + "@rc-component/color-picker": "~2.0.1", "@rc-component/mutate-observer": "^1.1.0", "@rc-component/qrcode": "~1.0.0", "@rc-component/tour": "~1.15.0", - "@rc-component/trigger": "^2.2.0", + "@rc-component/trigger": "^2.2.1", "classnames": "^2.5.1", "copy-to-clipboard": "^3.3.3", "dayjs": "^1.11.11", @@ -1036,34 +1093,34 @@ "rc-dialog": "~9.5.2", "rc-drawer": "~7.2.0", "rc-dropdown": "~4.2.0", - "rc-field-form": "~2.2.1", + "rc-field-form": "~2.4.0", "rc-image": "~7.9.0", - "rc-input": "~1.5.1", - "rc-input-number": "~9.1.0", - "rc-mentions": "~2.14.0", + "rc-input": "~1.6.3", + "rc-input-number": "~9.2.0", + "rc-mentions": "~2.15.0", "rc-menu": "~9.14.1", "rc-motion": "^2.9.2", "rc-notification": "~5.6.0", "rc-pagination": "~4.2.0", - "rc-picker": "~4.6.6", + "rc-picker": "~4.6.13", "rc-progress": "~4.0.0", "rc-rate": "~2.13.0", "rc-resize-observer": "^1.4.0", "rc-segmented": "~2.3.0", - "rc-select": "~14.15.0", - "rc-slider": "~10.6.2", + "rc-select": "~14.15.1", + "rc-slider": "~11.1.5", "rc-steps": "~6.0.1", "rc-switch": "~4.1.0", "rc-table": "~7.45.7", "rc-tabs": "~15.1.1", - "rc-textarea": "~1.7.0", + "rc-textarea": "~1.8.1", "rc-tooltip": "~6.2.0", "rc-tree": "~5.8.8", "rc-tree-select": "~5.22.1", - "rc-upload": "~4.5.2", + "rc-upload": "~4.7.0", "rc-util": "^5.43.0", "scroll-into-view-if-needed": "^3.1.0", - "throttle-debounce": "^5.0.0" + "throttle-debounce": "^5.0.2" }, "funding": { "type": "opencollective", @@ -1075,13 +1132,12 @@ } }, "node_modules/antd-img-crop": { - "version": "4.22.0", - "resolved": "https://registry.npmjs.org/antd-img-crop/-/antd-img-crop-4.22.0.tgz", - "integrity": "sha512-mfGujUUH+rf9L4ENx7yFJPkvtnEjvexWmR1/kycHzVUnMlC1UBtFMKcXgYvtNl/JTAW6jRVRc1asjzGr9idIZA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/antd-img-crop/-/antd-img-crop-4.23.0.tgz", + "integrity": "sha512-JtQoUmR3GqXoG+hsYXRxCBC60AgUKbbvArbnd8/5UmmuyVcQzBnumfoQTdC9wczWQuxRIpkPwsdOge6CCeepqg==", "dependencies": { - "compare-versions": "6.1.0", - "react-easy-crop": "^5.0.7", - "tslib": "^2.6.2" + "react-easy-crop": "^5.0.8", + "tslib": "^2.6.3" }, "peerDependencies": { "antd": ">=4.0.0", @@ -1243,18 +1299,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/array.prototype.toreversed": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/array.prototype.toreversed/-/array.prototype.toreversed-1.1.2.tgz", - "integrity": "sha512-wwDCoT4Ck4Cz7sLtgUmzR5UV3YF5mFHUlbChCzZBQZ+0m2cl/DH3tKgvphv1nKgFsJ48oCSg6p91q2Vm0I/ZMA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - } - }, "node_modules/array.prototype.tosorted": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", @@ -1305,9 +1349,9 @@ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/autoprefixer": { - "version": "10.4.19", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.19.tgz", - "integrity": "sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==", + "version": "10.4.20", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.20.tgz", + "integrity": "sha512-XY25y5xSv/wEoqzDyXXME4AFfkZI0P23z6Fs3YgymDnKJkCGOnkL0iTxCa85UTqaSgfcqyf3UA6+c7wUvx/16g==", "dev": true, "funding": [ { @@ -1324,11 +1368,11 @@ } ], "dependencies": { - "browserslist": "^4.23.0", - "caniuse-lite": "^1.0.30001599", + "browserslist": "^4.23.3", + "caniuse-lite": "^1.0.30001646", "fraction.js": "^4.3.7", "normalize-range": "^0.1.2", - "picocolors": "^1.0.0", + "picocolors": "^1.0.1", "postcss-value-parser": "^4.2.0" }, "bin": { @@ -1357,18 +1401,18 @@ } }, "node_modules/axe-core": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.9.1.tgz", - "integrity": "sha512-QbUdXJVTpvUTHU7871ppZkdOLBeGUKBQWHkHrvN2V9IQWGMt61zf3B45BtzjxEJzYuj0JBjBZP/hmYS/R9pmAw==", + "version": "4.10.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.10.0.tgz", + "integrity": "sha512-Mr2ZakwQ7XUAjp7pAwQWRhhK8mQQ6JAaNWSjmjxil0R8BPioMtQsTLOolGYkji1rcL++3dCqZA3zWqpT+9Ew6g==", "dev": true, "engines": { "node": ">=4" } }, "node_modules/axios": { - "version": "1.7.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", - "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.0", @@ -1425,9 +1469,9 @@ } }, "node_modules/browserslist": { - "version": "4.23.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", - "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", + "version": "4.23.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.3.tgz", + "integrity": "sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA==", "dev": true, "funding": [ { @@ -1444,10 +1488,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001629", - "electron-to-chromium": "^1.4.796", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.16" + "caniuse-lite": "^1.0.30001646", + "electron-to-chromium": "^1.5.4", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.0" }, "bin": { "browserslist": "cli.js" @@ -1471,7 +1515,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1504,9 +1547,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001639", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz", - "integrity": "sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==", + "version": "1.0.30001651", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz", + "integrity": "sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg==", "funding": [ { "type": "opencollective", @@ -1613,11 +1656,6 @@ "node": ">= 0.8" } }, - "node_modules/compare-versions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz", - "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg==" - }, "node_modules/compute-scroll-into-view": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/compute-scroll-into-view/-/compute-scroll-into-view-3.1.0.tgz", @@ -1629,6 +1667,49 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/contentful": { + "version": "10.15.0", + "resolved": "https://registry.npmjs.org/contentful/-/contentful-10.15.0.tgz", + "integrity": "sha512-gkkMRf2FK1SQHMs2UKOuIeCdBXQKF/fMzIRCDL038lUScyE6mvnPu8aHrAQuUZwfcd58J0cibqT+iqj+pAVyGA==", + "dependencies": { + "@contentful/content-source-maps": "^0.11.0", + "@contentful/rich-text-types": "^16.0.2", + "axios": "^1.7.4", + "contentful-resolve-response": "^1.9.0", + "contentful-sdk-core": "^8.3.1", + "json-stringify-safe": "^5.0.1", + "type-fest": "^4.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/contentful-resolve-response": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/contentful-resolve-response/-/contentful-resolve-response-1.9.0.tgz", + "integrity": "sha512-LtgPx/eREpHXOX82od48zFZbFhXzYw/NfUoYK4Qf1OaKpLzmYPE4cAY4aD+rxVgnMM5JN/mQaPCsofUlJRYEUA==", + "dependencies": { + "fast-copy": "^2.1.7" + }, + "engines": { + "node": ">=4.7.2" + } + }, + "node_modules/contentful-sdk-core": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/contentful-sdk-core/-/contentful-sdk-core-8.3.1.tgz", + "integrity": "sha512-HYy4ecFA76ERxz7P0jW7hgDcL8jH+bRckv2QfAwQ4k1yPP9TvxpZwrKnlLM69JOStxVkCXP37HvbjbFnjcoWdg==", + "dependencies": { + "fast-copy": "^2.1.7", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "p-throttle": "^4.1.1", + "qs": "^6.11.2" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/copy-to-clipboard": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/copy-to-clipboard/-/copy-to-clipboard-3.3.3.tgz", @@ -1732,14 +1813,14 @@ } }, "node_modules/dayjs": { - "version": "1.11.11", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.11.tgz", - "integrity": "sha512-okzr3f11N6WuqYtZSvm+F776mB41wRZMhKP+hc34YdW+KmtYYK9iqvHSwo2k9FEH3fhGXvOPV6yz2IcSrfRUDg==" + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==" }, "node_modules/debug": { - "version": "4.3.5", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz", - "integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dev": true, "dependencies": { "ms": "2.1.2" @@ -1795,7 +1876,6 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1864,9 +1944,9 @@ "dev": true }, "node_modules/electron-to-chromium": { - "version": "1.4.815", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.815.tgz", - "integrity": "sha512-OvpTT2ItpOXJL7IGcYakRjHCt8L5GrrN/wHCQsRB4PQa1X9fe+X9oen245mIId7s14xvArCGSTIq644yPUKKLg==", + "version": "1.5.13", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz", + "integrity": "sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q==", "dev": true }, "node_modules/emoji-regex": { @@ -1876,9 +1956,9 @@ "dev": true }, "node_modules/enhanced-resolve": { - "version": "5.17.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.0.tgz", - "integrity": "sha512-dwDPwZL0dmye8Txp2gzFmA6sxALaSvdRDjPH0viLcKrtlOL3tw62nWWweVD1SdILDTJrbrL6tdWVN58Wo6U3eA==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -1957,7 +2037,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -1969,7 +2048,6 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, "engines": { "node": ">= 0.4" } @@ -2148,12 +2226,12 @@ } }, "node_modules/eslint-config-next": { - "version": "14.2.4", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.4.tgz", - "integrity": "sha512-Qr0wMgG9m6m4uYy2jrYJmyuNlYZzPRQq5Kvb9IDlYwn+7yq6W6sfMNFgb+9guM1KYwuIo6TIaiFhZJ6SnQ/Efw==", + "version": "14.2.6", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-14.2.6.tgz", + "integrity": "sha512-z0URA5LO6y8lS/YLN0EDW/C4LEkDODjJzA37dvLVdzCPzuewjzTe1os5g3XclZAZrQ8X8hPaSMQ2JuVWwMmrTA==", "dev": true, "dependencies": { - "@next/eslint-plugin-next": "14.2.4", + "@next/eslint-plugin-next": "14.2.6", "@rushstack/eslint-patch": "^1.3.3", "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || 7.0.0 - 7.2.0", "eslint-import-resolver-node": "^0.3.6", @@ -2336,35 +2414,35 @@ } }, "node_modules/eslint-plugin-react": { - "version": "7.34.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.34.3.tgz", - "integrity": "sha512-aoW4MV891jkUulwDApQbPYTVZmeuSyFrudpbTAQuj5Fv8VL+o6df2xIGpw8B0hPjAaih1/Fb0om9grCdyFYemA==", + "version": "7.35.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.35.0.tgz", + "integrity": "sha512-v501SSMOWv8gerHkk+IIQBkcGRGrO2nfybfj5pLxuJNFTPxxA3PSryhXTK+9pNbtkggheDdsC0E9Q8CuPk6JKA==", "dev": true, "dependencies": { "array-includes": "^3.1.8", "array.prototype.findlast": "^1.2.5", "array.prototype.flatmap": "^1.3.2", - "array.prototype.toreversed": "^1.1.2", "array.prototype.tosorted": "^1.1.4", "doctrine": "^2.1.0", "es-iterator-helpers": "^1.0.19", "estraverse": "^5.3.0", + "hasown": "^2.0.2", "jsx-ast-utils": "^2.4.1 || ^3.0.0", "minimatch": "^3.1.2", "object.entries": "^1.1.8", "object.fromentries": "^2.0.8", - "object.hasown": "^1.1.4", "object.values": "^1.2.0", "prop-types": "^15.8.1", "resolve": "^2.0.0-next.5", "semver": "^6.3.1", - "string.prototype.matchall": "^4.0.11" + "string.prototype.matchall": "^4.0.11", + "string.prototype.repeat": "^1.0.0" }, "engines": { "node": ">=4" }, "peerDependencies": { - "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8" + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" } }, "node_modules/eslint-plugin-react-hooks": { @@ -2463,9 +2541,9 @@ } }, "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", "dev": true, "dependencies": { "estraverse": "^5.1.0" @@ -2504,6 +2582,11 @@ "node": ">=0.10.0" } }, + "node_modules/fast-copy": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-2.1.7.tgz", + "integrity": "sha512-ozrGwyuCTAy7YgFCua8rmqmytECYk/JYAMXcswOcm0qvGoE3tPb7ivBeIHTOK2DiapBhDZgacIhzhQIKU5TCfA==" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2669,10 +2752,15 @@ "is-callable": "^1.1.3" } }, + "node_modules/foreach": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.6.tgz", + "integrity": "sha512-k6GAGDyqLe9JaebCsFCoudPPWfihKu8pylYXRlqP1J7ms39iPoTtk2fviNglIeQEwdh0bQeKJ01ZPyuyQvKzwg==" + }, "node_modules/foreground-child": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.2.1.tgz", - "integrity": "sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -2746,7 +2834,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2782,7 +2869,6 @@ "version": "1.2.4", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -2815,9 +2901,9 @@ } }, "node_modules/get-tsconfig": { - "version": "4.7.5", - "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.5.tgz", - "integrity": "sha512-ZCuZCnlqNzjb4QprAzXKdpp/gh6KTxSJuw3IBsPnV/7fV4NxC9ckB+vPTt8w7fJA0TaSD7c55BR47JD6MEDyDw==", + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", "dev": true, "dependencies": { "resolve-pkg-maps": "^1.0.0" @@ -2904,6 +2990,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/globals/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/globalthis": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", @@ -2944,7 +3042,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dev": true, "dependencies": { "get-intrinsic": "^1.1.3" }, @@ -2985,7 +3082,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -2997,7 +3093,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3009,7 +3104,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -3036,7 +3130,6 @@ "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3045,18 +3138,18 @@ } }, "node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "engines": { "node": ">= 4" } }, "node_modules/immutable": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.6.tgz", - "integrity": "sha512-Ju0+lEMyzMVZarkTn/gqRpdqd5dOPaz1mCZ0SH3JV6iFw81PldE/PEB1hWVEA288HPt4WXW8O7AWxB10M+03QQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==", "devOptional": true }, "node_modules/import-fresh": { @@ -3226,9 +3319,9 @@ } }, "node_modules/is-core-module": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.14.0.tgz", - "integrity": "sha512-a5dFJih5ZLYlRtDc0dZWP7RiKr6xIKzmn/oAYCDvdLThadVgyJwlaoQPmRtMSpz+rk0OGAgIu+TcM9HUF0fk1A==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dev": true, "dependencies": { "hasown": "^2.0.2" @@ -3584,6 +3677,14 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-pointer": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/json-pointer/-/json-pointer-0.6.2.tgz", + "integrity": "sha512-vLWcKbOaXlO+jvRy4qNd+TI1QUPZzfJj1tpJ3vAXDych5XJf93ftpUKe5pKCrzyIIwgBJcOcCVRUfqQP25afBw==", + "dependencies": { + "foreach": "^2.0.4" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", @@ -3596,6 +3697,11 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + }, "node_modules/json2mq": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/json2mq/-/json2mq-0.2.0.tgz", @@ -3696,6 +3802,16 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3714,13 +3830,10 @@ } }, "node_modules/lru-cache": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.3.0.tgz", - "integrity": "sha512-CQl19J/g+Hbjbv4Y3mFNNXFEL/5t/KCg8POCuUqd4rMKjGG+j1ybER83hxV58zL+dFI1PTkt3GNFSHRt+d8qEQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "node_modules/merge2": { "version": "1.4.1", @@ -3876,9 +3989,9 @@ } }, "node_modules/next-intl": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.15.3.tgz", - "integrity": "sha512-jNc2xYzwv0Q4EQKvuHye9dXaDaneiP/ZCQC+AccyOQD6N9d/FZiSWT4wfVVD4B0IXC1Hhzj1QussUu+k3ynnTg==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-3.17.4.tgz", + "integrity": "sha512-ro3yNIaMNVhCmCdG6u9R00HllMdJXsGdKkBaBq75iM0sSnjLr7IytiGmCuZsUMDqCnGswXfXvs/FjI/lC8OAOw==", "funding": [ { "type": "individual", @@ -3886,9 +3999,9 @@ } ], "dependencies": { - "@formatjs/intl-localematcher": "^0.2.32", + "@formatjs/intl-localematcher": "^0.5.4", "negotiator": "^0.6.3", - "use-intl": "^3.15.3" + "use-intl": "^3.17.4" }, "peerDependencies": { "next": "^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0 || ^14.0.0", @@ -3941,9 +4054,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==", + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, "node_modules/normalize-path": { @@ -3982,7 +4095,6 @@ "version": "1.13.2", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", - "dev": true, "engines": { "node": ">= 0.4" }, @@ -4079,23 +4191,6 @@ "node": ">= 0.4" } }, - "node_modules/object.hasown": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/object.hasown/-/object.hasown-1.1.4.tgz", - "integrity": "sha512-FZ9LZt9/RHzGySlBARE3VF+gE26TxR38SdmqOqliuTnl9wrKulaQs+4dee1V+Io8VfxqzAfHu6YuRgUy8OHoTg==", - "dev": true, - "dependencies": { - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/object.values": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.0.tgz", @@ -4169,6 +4264,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-throttle": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/p-throttle/-/p-throttle-4.1.1.tgz", + "integrity": "sha512-TuU8Ato+pRTPJoDzYD4s7ocJYcNSEZRvlxoq3hcPI2kZDZ49IQ1Wkj7/gDJc3X7XiEAAvRGtDzdXJI0tC3IL1g==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pako": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", @@ -4271,9 +4377,9 @@ } }, "node_modules/postcss": { - "version": "8.4.39", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.39.tgz", - "integrity": "sha512-0vzE+lAiG7hZl1/9I8yzKLx3aR9Xbof3fBHKunvMfOCYAtMhrsnccJY2iTURb9EZd5+pLuiNV9/c/GZJOHsgIw==", + "version": "8.4.41", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", + "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", "dev": true, "funding": [ { @@ -4337,6 +4443,20 @@ "node": ">=6" } }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4451,9 +4571,9 @@ } }, "node_modules/rc-field-form": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.2.1.tgz", - "integrity": "sha512-uoNqDoR7A4tn4QTSqoWPAzrR7ZwOK5I+vuZ/qdcHtbKx+ZjEsTg7QXm2wk/jalDiSksAQmATxL0T5LJkRREdIA==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/rc-field-form/-/rc-field-form-2.4.0.tgz", + "integrity": "sha512-XZ/lF9iqf9HXApIHQHqzJK5v2w4mkUMsVqAzOyWVzoiwwXEavY6Tpuw7HavgzIoD+huVff4JghSGcgEfX6eycg==", "dependencies": { "@babel/runtime": "^7.18.0", "@rc-component/async-validator": "^5.0.3", @@ -4485,9 +4605,9 @@ } }, "node_modules/rc-input": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.5.1.tgz", - "integrity": "sha512-+nOzQJDeIfIpNP/SgY45LXSKbuMlp4Yap2y8c+ZpU7XbLmNzUd6+d5/S75sA/52jsVE6S/AkhkkDEAOjIu7i6g==", + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/rc-input/-/rc-input-1.6.3.tgz", + "integrity": "sha512-wI4NzuqBS8vvKr8cljsvnTUqItMfG1QbJoxovCgL+DX4eVUcHIjVwharwevIxyy7H/jbLryh+K7ysnJr23aWIA==", "dependencies": { "@babel/runtime": "^7.11.1", "classnames": "^2.2.1", @@ -4499,14 +4619,14 @@ } }, "node_modules/rc-input-number": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.1.0.tgz", - "integrity": "sha512-NqJ6i25Xn/AgYfVxynlevIhX3FuKlMwIFpucGG1h98SlK32wQwDK0zhN9VY32McOmuaqzftduNYWWooWz8pXQA==", + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/rc-input-number/-/rc-input-number-9.2.0.tgz", + "integrity": "sha512-5XZFhBCV5f9UQ62AZ2hFbEY8iZT/dm23Q1kAg0H8EvOgD3UDbYYJAayoVIkM3lQaCqYAW5gV0yV3vjw1XtzWHg==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/mini-decimal": "^1.0.1", "classnames": "^2.2.5", - "rc-input": "~1.5.0", + "rc-input": "~1.6.0", "rc-util": "^5.40.1" }, "peerDependencies": { @@ -4515,16 +4635,16 @@ } }, "node_modules/rc-mentions": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.14.0.tgz", - "integrity": "sha512-qKR59FMuF8PK4ZqsbWX3UuA5P1M/snzyqV6Yt3y1DCFbCEdqUGIBgQp6vEfLCO6Z0RoRFlzXtCeSlBTcDDpg1A==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/rc-mentions/-/rc-mentions-2.15.0.tgz", + "integrity": "sha512-f5v5i7VdqvBDXbphoqcQWmXDif2Msd2arritVoWybrVDuHE6nQ7XCYsybHbV//WylooK52BFDouFvyaRDtXZEw==", "dependencies": { "@babel/runtime": "^7.22.5", "@rc-component/trigger": "^2.0.0", "classnames": "^2.2.6", - "rc-input": "~1.5.0", + "rc-input": "~1.6.0", "rc-menu": "~9.14.0", - "rc-textarea": "~1.7.0", + "rc-textarea": "~1.8.0", "rc-util": "^5.34.1" }, "peerDependencies": { @@ -4611,9 +4731,9 @@ } }, "node_modules/rc-picker": { - "version": "4.6.6", - "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.6.6.tgz", - "integrity": "sha512-sEWARCNlodubP7/IM6nXHDDIuKZ3gYd5CpS2cYcNORa2telX3nAfllqnGSGjGEFvdtyW+IqGAVatLOmFT0lKYg==", + "version": "4.6.13", + "resolved": "https://registry.npmjs.org/rc-picker/-/rc-picker-4.6.13.tgz", + "integrity": "sha512-yi4JWPGjm420Q8rHjZ6YNy2c5IfV+9EAzx2pewVRPOjJqfg7uifO/Z0uqxdl/h6AhBocuvRvtlyz6ehrAvTq7A==", "dependencies": { "@babel/runtime": "^7.24.7", "@rc-component/trigger": "^2.0.0", @@ -4710,9 +4830,9 @@ } }, "node_modules/rc-select": { - "version": "14.15.0", - "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.15.0.tgz", - "integrity": "sha512-BDqnDLhhm/8VyyyDlX7ju06S75k6ObJvbsN86zqZ4SY1Fu2ANQxeSWPo7pnwx5nwA5JgG+HcQevtddAgsdeBVQ==", + "version": "14.15.1", + "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-14.15.1.tgz", + "integrity": "sha512-mGvuwW1RMm1NCSI8ZUoRoLRK51R2Nb+QJnmiAvbDRcjh2//ulCkxeV6ZRFTECPpE1t2DPfyqZMPw90SVJzQ7wQ==", "dependencies": { "@babel/runtime": "^7.10.1", "@rc-component/trigger": "^2.1.1", @@ -4731,9 +4851,9 @@ } }, "node_modules/rc-slider": { - "version": "10.6.2", - "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-10.6.2.tgz", - "integrity": "sha512-FjkoFjyvUQWcBo1F3RgSglky3ar0+qHLM41PlFVYB4Bj3RD8E/Mv7kqMouLFBU+3aFglMzzctAIWRwajEuueSw==", + "version": "11.1.5", + "resolved": "https://registry.npmjs.org/rc-slider/-/rc-slider-11.1.5.tgz", + "integrity": "sha512-b77H5PbjMKsvkYXAYIkn50QuFX6ICQmCTibDinI9q+BHx65/TV4TeU25+oadhSRzykxs0/vBWeKBwRyySOeWlg==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.5", @@ -4820,13 +4940,13 @@ } }, "node_modules/rc-textarea": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.7.0.tgz", - "integrity": "sha512-UxizYJkWkmxP3zofXgc487QiGyDmhhheDLLjIWbFtDmiru1ls30KpO8odDaPyqNUIy9ugj5djxTEuezIn6t3Jg==", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/rc-textarea/-/rc-textarea-1.8.1.tgz", + "integrity": "sha512-bm36N2ZqwZAP60ZQg2OY9mPdqWC+m6UTjHc+CqEZOxb3Ia29BGHazY/s5bI8M4113CkqTzhtFUDNA078ZiOx3Q==", "dependencies": { "@babel/runtime": "^7.10.1", "classnames": "^2.2.1", - "rc-input": "~1.5.0", + "rc-input": "~1.6.0", "rc-resize-observer": "^1.0.0", "rc-util": "^5.27.0" }, @@ -4885,9 +5005,9 @@ } }, "node_modules/rc-upload": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.5.2.tgz", - "integrity": "sha512-QO3ne77DwnAPKFn0bA5qJM81QBjQi0e0NHdkvpFyY73Bea2NfITiotqJqVjHgeYPOJu5lLVR32TNGP084aSoXA==", + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/rc-upload/-/rc-upload-4.7.0.tgz", + "integrity": "sha512-eUwxYNHlsYe5vYhKFAUGrQG95JrnPzY+BmPi1Daq39fWNl/eOc7v4UODuWrVp2LFkQBuV3cMCG/I68iub6oBrg==", "dependencies": { "@babel/runtime": "^7.18.3", "classnames": "^2.2.5", @@ -4958,9 +5078,9 @@ } }, "node_modules/react-easy-crop": { - "version": "5.0.7", - "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.7.tgz", - "integrity": "sha512-6d5IUt09M3HwdDGwrcjPVgfrOfYWAOku8sCTn/xU7b1vkEg+lExMLwW8UbR39L8ybQi0hJZTU57yprF9h5Q5Ig==", + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/react-easy-crop/-/react-easy-crop-5.0.8.tgz", + "integrity": "sha512-KjulxXhR5iM7+ATN2sGCum/IyDxGw7xT0dFoGcqUP+ysaPU5Ka7gnrDa2tUHFHUoMNyPrVZ05QA+uvMgC5ym/g==", "dependencies": { "normalize-wheel": "^1.0.1", "tslib": "^2.0.1" @@ -5194,9 +5314,9 @@ } }, "node_modules/sass": { - "version": "1.77.6", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.6.tgz", - "integrity": "sha512-ByXE1oLD79GVq9Ht1PeHWCPMPB8XHpBuz1r85oByKHjZY6qV6rWnQovQzXJXuQ/XyE1Oj3iPk3lo28uzaRA2/Q==", + "version": "1.77.8", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.77.8.tgz", + "integrity": "sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ==", "devOptional": true, "dependencies": { "chokidar": ">=3.0.0 <4.0.0", @@ -5232,9 +5352,9 @@ "integrity": "sha512-d7wDPgDV3DDiqulJjKiV2865wKsJ34YI+NDREbm+FySq6WuKOikwyNQcm+doLAZ1O6ltdO0SeKle2xMpN3Brgw==" }, "node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, "bin": { "semver": "bin/semver.js" @@ -5247,7 +5367,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -5305,7 +5424,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", - "dev": true, "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -5482,6 +5600,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, "node_modules/string.prototype.trim": { "version": "1.2.9", "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.9.tgz", @@ -5578,9 +5706,9 @@ } }, "node_modules/styled-components": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", - "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", "dependencies": { "@emotion/is-prop-valid": "1.2.2", "@emotion/unitless": "0.8.1", @@ -5636,6 +5764,11 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/styled-components/node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "node_modules/styled-components/node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5664,9 +5797,9 @@ } }, "node_modules/stylis": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", - "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + "version": "4.3.3", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.3.tgz", + "integrity": "sha512-VtF42zBHvdPi561i9mAcPlWOUonfbCtXa7qdGI+Ro4qMP8TEb+7GpbGWD1+v2TS4nohQ0m8g1FhTVmRdcIsxdQ==" }, "node_modules/supports-color": { "version": "7.2.0", @@ -5774,12 +5907,11 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, + "version": "4.25.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.25.0.tgz", + "integrity": "sha512-bRkIGlXsnGBRBQRAY56UXBm//9qH4bmJfFvq83gSz41N282df+fjy8ofcEgc1sM8geNt5cl6mC2g9Fht1cs8Aw==", "engines": { - "node": ">=10" + "node": ">=16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5859,9 +5991,9 @@ } }, "node_modules/typescript": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz", - "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==", + "version": "5.5.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz", + "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==", "dev": true, "bin": { "tsc": "bin/tsc", @@ -5909,15 +6041,15 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz", + "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==", "dev": true, "funding": [ { @@ -5954,10 +6086,11 @@ } }, "node_modules/use-intl": { - "version": "3.15.3", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.15.3.tgz", - "integrity": "sha512-cHSeFy2cy4u6tT8A7KAcDbs+Hz6lytXClVSsOI1leD6OOrpakNxsmyLa8SMrttOAUQto5kV1f4LVhiX/lpkO3g==", + "version": "3.17.4", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-3.17.4.tgz", + "integrity": "sha512-6t3tScvli9TvIBwordjZul59ubYzStcMTCgYJEkEikVGqBJKzfpdpifZhRTU7CxgSoB63rt9+AOPGKklXvtebA==", "dependencies": { + "@formatjs/fast-memoize": "^2.2.0", "intl-messageformat": "^10.5.14" }, "peerDependencies": { @@ -6028,13 +6161,13 @@ } }, "node_modules/which-builtin-type": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.3.tgz", - "integrity": "sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.1.4.tgz", + "integrity": "sha512-bppkmBSsHFmIMSl8BO9TbsyzsvGjVoppt8xUiGzwiu/bhDCGxnpOKCxgqj6GuyHE0mINMDecBFPlOm2hzY084w==", "dev": true, "dependencies": { - "function.prototype.name": "^1.1.5", - "has-tostringtag": "^1.0.0", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", "is-async-function": "^2.0.0", "is-date-object": "^1.0.5", "is-finalizationregistry": "^1.0.2", @@ -6043,8 +6176,8 @@ "is-weakref": "^1.0.2", "isarray": "^2.0.5", "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.9" + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.15" }, "engines": { "node": ">= 0.4" From 77d3c8f66b0ab5c27c37240651eb17a188a0d52f Mon Sep 17 00:00:00 2001 From: dzfelix Date: Mon, 26 Aug 2024 23:29:30 +0400 Subject: [PATCH 16/23] main fix --- src/app/[locale]/(main)/@news/page.tsx | 77 ++++++++------------------ src/app/[locale]/blog/[slug]/page.tsx | 3 +- src/lib/contentful/blogPosts.ts | 10 +++- 3 files changed, 34 insertions(+), 56 deletions(-) diff --git a/src/app/[locale]/(main)/@news/page.tsx b/src/app/[locale]/(main)/@news/page.tsx index 2aa2a09..2dadcf5 100644 --- a/src/app/[locale]/(main)/@news/page.tsx +++ b/src/app/[locale]/(main)/@news/page.tsx @@ -1,71 +1,40 @@ import React from 'react'; import { useTranslations } from 'next-intl'; -import { unstable_setRequestLocale } from 'next-intl/server'; +import {getTranslations, unstable_setRequestLocale} from 'next-intl/server'; import { i18nText } from '../../../../i18nKeys'; +import {fetchBlogPosts} from "../../../../lib/contentful/blogPosts"; +import Link from "next/link"; -export default function News({ params: { locale } }: { params: { locale: string }}) { +export default async function News({params: {locale}}: { params: { locale: string } }) { unstable_setRequestLocale(locale); - const t = useTranslations('Main'); + const t = await getTranslations('Main'); + const {data, total} = await fetchBlogPosts({preview: false, sticky: true}) return (

    {t('news')}

    -
    -
    -
    - -
    -
    -
    News Headline
    -
    - The program not only focuses on a financial perspective, but allows you to study - performance from many angles, such as human resources management, IT, operations - management, risks etc. + {data.map((item, i) => ( +
    +
    +
    + {item.listImage?.alt}/ +
    +
    +
    {item.title}
    +
    + {item.excerpt} +
    + + {i18nText('readMore', locale)} + +
    - {i18nText('readMore', locale)} - -
    -
    -
    -
    -
    - -
    -
    -
    News Headline
    -
    - The program not only focuses on a financial perspective, but allows you to study - performance from many angles, such as human resources management, IT, operations - management, risks etc. -
    - {i18nText('readMore', locale)} - - -
    -
    -
    -
    -
    -
    - -
    -
    -
    News Headline
    -
    - The program not only focuses on a financial perspective, but allows you to study - performance from many angles, such as human resources management, IT, operations - management, risks etc. -
    - {i18nText('readMore', locale)} - - -
    -
    -
    + ))} +
    diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index 2f6aefd..f344216 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -22,7 +22,8 @@ export async function generateMetadata({ params }: BlogPostPageProps, parent: Re } return { - title: blogPost.title + title: blogPost.title, + // description: blogPost.metaDescription } } diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts index 415792c..92b75fd 100644 --- a/src/lib/contentful/blogPosts.ts +++ b/src/lib/contentful/blogPosts.ts @@ -68,8 +68,9 @@ interface FetchBlogPostsOptions { local?: string category?: string page?: number + sticky?: boolean } -export async function fetchBlogPosts({ preview, category, page }: FetchBlogPostsOptions): Promise<{ +export async function fetchBlogPosts({ preview, category, page, sticky }: FetchBlogPostsOptions): Promise<{ total: number; data: BlogPost[] }> { @@ -84,11 +85,18 @@ export async function fetchBlogPosts({ preview, category, page }: FetchBlogPosts query['fields.category.sys.contentType.sys.id']='blogPostCategory' } + if(page){ query['limit'] = pageSize query['skip'] = pageSize * (page - 1) } + if (sticky){ // только три для главной + query['fields.sticky'] = 1 + query['limit'] = 3 + query['skip'] = 0 + } + const blogPostsResult = await contentful.getEntries(query) const data = blogPostsResult.items.map((blogPostEntry) => parseContentfulBlogPost(blogPostEntry) as BlogPost) From 28f5babf2250d672df577d8050c1e04df19bc049 Mon Sep 17 00:00:00 2001 From: dzfelix Date: Tue, 27 Aug 2024 17:01:29 +0300 Subject: [PATCH 17/23] fix meta anf first page --- src/app/[locale]/blog/[slug]/page.tsx | 4 ++-- src/lib/contentful/blogPosts.ts | 2 +- src/types/blogPost.ts | 2 ++ 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index f344216..ca1938a 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import type { Metadata } from 'next'; +import type {Metadata, ResolvingMetadata} from 'next'; import { draftMode } from 'next/headers' import { notFound } from 'next/navigation'; import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/blogPosts"; @@ -23,7 +23,7 @@ export async function generateMetadata({ params }: BlogPostPageProps, parent: Re return { title: blogPost.title, - // description: blogPost.metaDescription + description: blogPost.metaDescription } } diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts index 92b75fd..8c670a8 100644 --- a/src/lib/contentful/blogPosts.ts +++ b/src/lib/contentful/blogPosts.ts @@ -77,7 +77,7 @@ export async function fetchBlogPosts({ preview, category, page, sticky }: FetchB const contentful = contentfulClient({ preview }) const query = { content_type: 'blogPost', - select: ['fields.title', 'fields.excerpt', 'fields.author', 'fields.listImage', 'fields.author', 'fields.category', 'sys.createdAt', 'fields.slug'], + select: ['fields.title', 'fields.excerpt', 'fields.author', 'fields.listImage', 'fields.author', 'fields.category', 'sys.createdAt', 'fields.slug', 'fields.metaDescription'], order: ['sys.createdAt'], } if (category){ diff --git a/src/types/blogPost.ts b/src/types/blogPost.ts index c4776c4..5304b7d 100644 --- a/src/types/blogPost.ts +++ b/src/types/blogPost.ts @@ -9,6 +9,7 @@ export interface BlogPostFields { title?: EntryFieldTypes.Symbol slug: EntryFieldTypes.Symbol excerpt: EntryFieldTypes.Symbol + metaDescription: EntryFieldTypes.Symbol listImage?: EntryFieldTypes.AssetLink author?: AuthorSkeleton category: BlogPostCategorySkeleton @@ -28,6 +29,7 @@ export interface BlogPost { author: Author | null category: string createdAt: string + metaDescription: string body: Array } From cda91b9ea998ae61822ee054199ddea4514e70bb Mon Sep 17 00:00:00 2001 From: dzfelix Date: Wed, 28 Aug 2024 12:28:30 +0300 Subject: [PATCH 18/23] breadcrumbs & author link --- src/app/[locale]/blog/[slug]/page.tsx | 37 +++++++++++++++++----- src/components/BlogPosts/BlogPostsList.tsx | 2 ++ src/lib/contentful/authors.ts | 1 + src/lib/contentful/blogPosts.ts | 1 + src/types/author.ts | 2 ++ src/types/blogPost.ts | 1 + 6 files changed, 36 insertions(+), 8 deletions(-) diff --git a/src/app/[locale]/blog/[slug]/page.tsx b/src/app/[locale]/blog/[slug]/page.tsx index ca1938a..df0e865 100644 --- a/src/app/[locale]/blog/[slug]/page.tsx +++ b/src/app/[locale]/blog/[slug]/page.tsx @@ -5,9 +5,11 @@ import { notFound } from 'next/navigation'; import {fetchBlogPost, fetchBlogPosts, Widget} from "../../../../lib/contentful/blogPosts"; import Util from "node:util"; import RichText from "../../../../lib/contentful/RichText"; +import Link from "next/link"; interface BlogPostPageParams { slug: string + locale: string } interface BlogPostPageProps { @@ -61,23 +63,25 @@ export default async function BlogItem({params}: { params: BlogPostPageParams })
    - +
    - -
    -
    {item.author?.name}
    -
    {item.createdAt}
    -
    + + +
    +
    {item.author?.name}
    +
    {item.createdAt}
    +
    +
    - + 165
    - + Share
    @@ -86,6 +90,23 @@ export default async function BlogItem({params}: { params: BlogPostPageParams }) {item.body.map(renderWidget)}
    +
    + +
    +
    + ); }; diff --git a/src/components/BlogPosts/BlogPostsList.tsx b/src/components/BlogPosts/BlogPostsList.tsx index 954402d..7a26f94 100644 --- a/src/components/BlogPosts/BlogPostsList.tsx +++ b/src/components/BlogPosts/BlogPostsList.tsx @@ -44,6 +44,7 @@ export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE
  22. +
    @@ -51,6 +52,7 @@ export const BlogPostsList = ({ basePath = '/', locale, pageSize = DEFAULT_PAGE
    {item.createdAt}
    +
    diff --git a/src/lib/contentful/authors.ts b/src/lib/contentful/authors.ts index 57a2f30..d378554 100644 --- a/src/lib/contentful/authors.ts +++ b/src/lib/contentful/authors.ts @@ -11,6 +11,7 @@ export function parseContentfulAuthor(authorEntry?: AuthorEntry): Auth return { name: authorEntry.fields.name || '', + expertId: authorEntry.fields.expertId || '', avatar: parseContentfulContentImage(authorEntry.fields.avatar), } } \ No newline at end of file diff --git a/src/lib/contentful/blogPosts.ts b/src/lib/contentful/blogPosts.ts index 8c670a8..2ab08eb 100644 --- a/src/lib/contentful/blogPosts.ts +++ b/src/lib/contentful/blogPosts.ts @@ -59,6 +59,7 @@ export function parseContentfulBlogPost(entry?: PostEntry): BlogPost | null { author: parseContentfulAuthor(entry.fields.author), createdAt: dayjs(entry.sys.createdAt).format('MMM DD, YYYY'), category: entry.fields.category.fields.title, + categorySlug: entry.fields.category.fields.slug, body: parseWidgets(entry.fields.body) || [] } } diff --git a/src/types/author.ts b/src/types/author.ts index 05c7eac..f836cc4 100644 --- a/src/types/author.ts +++ b/src/types/author.ts @@ -5,10 +5,12 @@ import {ContentImage} from "../lib/contentful/contentImage"; export interface AuthorFields { name: EntryFieldTypes.Symbol avatar: EntryFieldTypes.AssetLink + expertId: EntryFieldTypes.AssetLink } export interface Author { name: string + expertId: string avatar: ContentImage | null } diff --git a/src/types/blogPost.ts b/src/types/blogPost.ts index 5304b7d..f97d749 100644 --- a/src/types/blogPost.ts +++ b/src/types/blogPost.ts @@ -28,6 +28,7 @@ export interface BlogPost { listImage: ContentImage | null author: Author | null category: string + categorySlug: string createdAt: string metaDescription: string body: Array From b52096b3bc7f13a5b4b8077f7bafe67ec6c15d9b Mon Sep 17 00:00:00 2001 From: SD Date: Thu, 5 Sep 2024 20:06:00 +0400 Subject: [PATCH 19/23] feat: add edit modal for expert's schedule --- .../account/(account)/expert-profile/page.tsx | 18 +- src/app/[locale]/experts/[expertId]/page.tsx | 3 +- src/app/sitemap.jsx | 4 +- .../ExpertProfile/ExpertProfile.tsx | 8 +- .../ExpertProfile/content/ExpertAbout.tsx | 18 +- .../ExpertProfile/content/ExpertSchedule.tsx | 30 ++- .../Modals/EditExpertScheduleModal.tsx | 214 ++++++++++++++++++ src/components/view/CustomMultiSelect.tsx | 4 +- src/components/view/CustomSelect.tsx | 8 +- src/components/view/CustomTimePicker.tsx | 48 ++++ src/constants/time.ts | 37 +++ src/i18nKeys/de.ts | 1 + src/i18nKeys/en.ts | 1 + src/i18nKeys/es.ts | 1 + src/i18nKeys/fr.ts | 1 + src/i18nKeys/it.ts | 1 + src/i18nKeys/ru.ts | 1 + src/styles/_pages.scss | 12 + src/styles/view/_select.scss | 14 ++ src/styles/view/_timepicker.scss | 59 +++++ src/styles/view/style.scss | 1 + src/types/profile.ts | 8 +- src/types/schedule.ts | 9 +- src/utils/time.ts | 204 +++++++++++++++-- 24 files changed, 637 insertions(+), 68 deletions(-) create mode 100644 src/components/Modals/EditExpertScheduleModal.tsx create mode 100644 src/components/view/CustomTimePicker.tsx create mode 100644 src/constants/time.ts create mode 100644 src/styles/view/_timepicker.scss diff --git a/src/app/[locale]/account/(account)/expert-profile/page.tsx b/src/app/[locale]/account/(account)/expert-profile/page.tsx index 3eaea64..7cda5f2 100644 --- a/src/app/[locale]/account/(account)/expert-profile/page.tsx +++ b/src/app/[locale]/account/(account)/expert-profile/page.tsx @@ -38,10 +38,6 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo getPayData(locale, jwt) ]) .then(([profile, person, education, tags, practice, schedule, payData]) => { - console.log('profile', profile); - console.log('person', person); - console.log('education', education); - console.log('schedule', schedule); setIsFull(profile.fillProgress === 'full'); setData({ person, @@ -63,14 +59,12 @@ export default function ExpertProfilePage({ params: { locale } }: { params: { lo return ( - {data && ( - - )} + ); }; diff --git a/src/app/[locale]/experts/[expertId]/page.tsx b/src/app/[locale]/experts/[expertId]/page.tsx index fe65fc6..3da5bee 100644 --- a/src/app/[locale]/experts/[expertId]/page.tsx +++ b/src/app/[locale]/experts/[expertId]/page.tsx @@ -9,7 +9,7 @@ import { ExpertInformation, ExpertPractice } from '../../../../components/Experts/ExpertDetails'; -import { Details } from '../../../../types/experts'; +import { Details } from '../../../../types/education'; import { BackButton } from '../../../../components/view/BackButton'; import { i18nText } from '../../../../i18nKeys'; @@ -36,7 +36,6 @@ export default async function ExpertItem({ params: { expertId = '', locale } }: if (!expertId) notFound(); const expert = await getExpertById(expertId, locale); - console.log(expert); const getAssociationLevel = (accLevelId?: number) => { if (accLevelId) { diff --git a/src/app/sitemap.jsx b/src/app/sitemap.jsx index 84e710a..4dd1000 100644 --- a/src/app/sitemap.jsx +++ b/src/app/sitemap.jsx @@ -1,4 +1,4 @@ -import {fetchBlogPosts} from "../lib/contentful/blogPosts"; +import { fetchBlogPosts } from '../lib/contentful/blogPosts'; export default async function sitemap() { const paths = [ @@ -24,4 +24,4 @@ export default async function sitemap() { }) return paths -} \ No newline at end of file +} diff --git a/src/components/ExpertProfile/ExpertProfile.tsx b/src/components/ExpertProfile/ExpertProfile.tsx index fa7e13a..c7e1e71 100644 --- a/src/components/ExpertProfile/ExpertProfile.tsx +++ b/src/components/ExpertProfile/ExpertProfile.tsx @@ -145,7 +145,13 @@ export const ExpertProfile = ({ locale, data, updateData, isFull }: ExpertProfil updateExpert={updateExpert} /> - + + + void; }; -export const ExpertSchedule = ({ locale, data }: ExpertScheduleProps) => { +export const ExpertSchedule = ({ locale, data, updateExpert }: ExpertScheduleProps) => { const [showEdit, setShowEdit] = useState(false); - // person51 return (

    {i18nText('schedule', locale)}

    - {/*} onClick={() => setShowEdit(true)} - />*/} + />
    {data && data?.workingTimes?.map((date, index) => { - const { startDay, startTime, endDay, endTime } = getCurrentTime(date); + const { startDay, startTimeMin, endTimeMin } = getCurrentTime(date, dayjs().format('Z')); return (
    {i18nText(startDay, locale)} -
    {startTime}
    +
    {startTimeMin ? getTimeString(startTimeMin) : '00:00'}
    - - {startDay !== endDay && {i18nText(endDay, locale)}} -
    {endTime}
    +
    {endTimeMin ? getTimeString(endTimeMin) : '00:00'}
    ) })}
    + setShowEdit(false)} + locale={locale} + data={data} + refresh={() => updateExpert('schedule')} + />
    ); }; diff --git a/src/components/Modals/EditExpertScheduleModal.tsx b/src/components/Modals/EditExpertScheduleModal.tsx new file mode 100644 index 0000000..58e516d --- /dev/null +++ b/src/components/Modals/EditExpertScheduleModal.tsx @@ -0,0 +1,214 @@ +'use client'; + +import React, { FC, useEffect, useState } from 'react'; +import { Modal, Button, message } from 'antd'; +import { CloseOutlined, DeleteOutlined } from '@ant-design/icons'; +import dayjs from 'dayjs'; +import { i18nText } from '../../i18nKeys'; +import { AUTH_TOKEN_KEY } from '../../constants/common'; +import { UTC_LIST } from '../../constants/time'; +import { MapWorkingTime, ScheduleDTO } from '../../types/schedule'; +import { + WEEK_DAY, + formattedSchedule, + getNewTime, + getTimeZoneOffset, + getTimeString, + formattedTimeByOffset, formattedWorkList +} from '../../utils/time'; +import { useLocalStorage } from '../../hooks/useLocalStorage'; +import { setSchedule } from '../../actions/profile'; +import { CustomSelect } from '../view/CustomSelect'; +import { CustomTimePicker } from '../view/CustomTimePicker'; +import { LinkButton } from '../view/LinkButton'; +import { OutlinedButton } from '../view/OutlinedButton'; + +type EditExpertScheduleModalProps = { + open: boolean; + handleCancel: () => void; + locale: string; + data?: ScheduleDTO; + refresh: () => void; +}; + +const DEFAULT_WORK: MapWorkingTime = { startDay: '' }; + +export const EditExpertScheduleModal: FC = ({ + open, + handleCancel, + locale, + data, + refresh, +}) => { + const defaultTimeZone = dayjs().format('Z'); + const [jwt] = useLocalStorage(AUTH_TOKEN_KEY, ''); + const [timeZone, setTimeZone] = useState(defaultTimeZone); + const [workList, setWorkList] = useState([DEFAULT_WORK]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + if (open && data?.workingTimes && data.workingTimes.length > 0) { + setWorkList(formattedSchedule(data.workingTimes, timeZone)); + } + }, [open]); + + const onSave = () => { + const workingTimes = formattedWorkList(workList, timeZone); + + setLoading(true); + setSchedule(locale, jwt, { workingTimes }) + .then(() => { + handleCancel(); + refresh(); + }) + .catch(() => { + message.error('Не удалось сохранить расписание'); + }) + .finally(() => { + setLoading(false); + }) + }; + + const addWorkingHours = () => { + setWorkList([ + ...workList, + DEFAULT_WORK + ]); + }; + + const deleteWorkingHours = (index: number) => { + setWorkList(workList.filter((work, i) => i !== index)); + }; + + const onChangeWeekDay = (val: string, index: number) => { + setWorkList(workList.map((work, i) => { + if (i === index) { + return { + ...work, + startDay: val + } + } + + return work; + })); + }; + + const onChangeTime = (time: string, index: number, start?: boolean) => { + setWorkList(workList.map((work, i) => { + if (i === index) { + const timeMin = getNewTime(time); + let res; + + if (start) { + res = { + startTimeMin: timeMin + } + } else { + res = { + endTimeMin: timeMin + } + } + + return { + ...work, + ...res + } + } + + return work; + })); + }; + + const onChangeTimeZone = (newTimeZone: string) => { + const offset = getTimeZoneOffset(timeZone, newTimeZone); + setTimeZone(newTimeZone); + setWorkList(workList.map((work) => formattedTimeByOffset(work, offset))); + } + + return ( + } + > +
    +
    {i18nText('schedule', locale)}
    +
    +
    +
    +
    +
    +
    {`${i18nText('yourTimezone', locale)}: ${defaultTimeZone}`}
    +
    + ({ value, label: value }))} + onChange={(val) => onChangeTimeZone(val)} + /> +
    +
    +

    {i18nText('workTime', locale)}

    +
    + {workList.length === 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => ( +
    + + + +
    + )) : null} + {workList.length > 1 ? workList.map(({ startDay, startTimeMin, endTimeMin }, index) => ( +
    + ({ value, label: i18nText(value, locale) }))} + onChange={(val) => onChangeWeekDay(val, index)} + /> + onChangeTime(timeString, index, true)} + /> + onChangeTime(timeString, index)} + /> + } + onClick={() => deleteWorkingHours(index)} + /> +
    + )) : null} +
    + + {i18nText('addWorkingHours', locale)} + +
    +
    +
    +
    +
    + +
    +
    +
    + ); +}; diff --git a/src/components/view/CustomMultiSelect.tsx b/src/components/view/CustomMultiSelect.tsx index 1ea80ef..32b92d3 100644 --- a/src/components/view/CustomMultiSelect.tsx +++ b/src/components/view/CustomMultiSelect.tsx @@ -30,7 +30,9 @@ export const CustomMultiSelect = (props: any) => { return (
    -
    {label}
    +
    + {label} +
    { + const { label, value, ...other } = props; + const [isActiveLabel, setIsActiveLabel] = useState(false); + + useEffect(() => { + if (label) { + setIsActiveLabel(!!value); + } else { + setIsActiveLabel(false); + } + }, [value]); + + const onOpenChange = (open: boolean) => { + if (open) { + if (!isActiveLabel) setIsActiveLabel(true) + } else { + setIsActiveLabel(!!value) + } + }; + + return ( +
    +
    + {label} +
    + } + {...other} + /> +
    + ); +}; diff --git a/src/constants/time.ts b/src/constants/time.ts new file mode 100644 index 0000000..f6819e4 --- /dev/null +++ b/src/constants/time.ts @@ -0,0 +1,37 @@ +export const UTC_LIST = [ + '-12:00', + '-11:00', + '-10:00', + '-09:30', + '-09:00', + '-08:00', + '-07:00', + '-06:00', + '-05:00', + '-04:00', + '-03:30', + '-03:00', + '-02:00', + '-01:00', + '+00:00', + '+01:00', + '+02:00', + '+03:00', + '+03:30', + '+04:00', + '+04:30', + '+05:00', + '+05:30', + '+06:00', + '+06:30', + '+07:00', + '+08:00', + '+09:00', + '+09:30', + '+10:00', + '+10:30', + '+11:00', + '+12:00', + '+13:00', + '+14:00' +]; diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index a12d82f..3a2f8c0 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -126,6 +126,7 @@ export default { workTime: 'Arbeitszeit', startAt: 'Beginn um', finishAt: 'Ende um', + day: 'Tag', addWorkingHours: 'Arbeitszeiten hinzufügen', specialisation: 'Spezialisierung', selectSpecialisation: 'Wählen Sie Ihre Spezialisierung, um fortzufahren', diff --git a/src/i18nKeys/en.ts b/src/i18nKeys/en.ts index 117a2b7..4be1bc4 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -126,6 +126,7 @@ export default { workTime: 'Work time', startAt: 'Start at', finishAt: 'Finish at', + day: 'Day', addWorkingHours: 'Add working hours', specialisation: 'Specialisation', selectSpecialisation: 'Select your specialisation to proceed', diff --git a/src/i18nKeys/es.ts b/src/i18nKeys/es.ts index 5f4a314..f869ff2 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -126,6 +126,7 @@ export default { workTime: 'Tiempo de trabajo', startAt: 'Empieza a las', finishAt: 'Termina a las', + day: 'Día', addWorkingHours: 'Añadir horas de trabajo', specialisation: 'Especialización', selectSpecialisation: 'Selecciona tu especialización para continuar', diff --git a/src/i18nKeys/fr.ts b/src/i18nKeys/fr.ts index 3502aef..6bad2ed 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -126,6 +126,7 @@ export default { workTime: 'Heures de travail', startAt: 'Commencer à', finishAt: 'Finir à', + day: 'Jour', addWorkingHours: 'Ajouter des heures de travail', specialisation: 'Spécialisation', selectSpecialisation: 'Sélectionnez votre spécialisation pour continuer', diff --git a/src/i18nKeys/it.ts b/src/i18nKeys/it.ts index 8a3743a..81a7d39 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -126,6 +126,7 @@ export default { workTime: 'Orario di lavoro', startAt: 'Inizia alle', finishAt: 'Termina alle', + day: 'Giorno', addWorkingHours: 'Aggiungi ore lavorative', specialisation: 'Specializzazione', selectSpecialisation: 'Seleziona la tua specializzazione per continuare', diff --git a/src/i18nKeys/ru.ts b/src/i18nKeys/ru.ts index de2a14b..b7db7b9 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -126,6 +126,7 @@ export default { workTime: 'Рабочее время', startAt: 'Начало в', finishAt: 'Завершение в', + day: 'День', addWorkingHours: 'Добавить рабочие часы', specialisation: 'Специализация', selectSpecialisation: 'Выберите свою специализацию для продолжения', diff --git a/src/styles/_pages.scss b/src/styles/_pages.scss index 394a403..eb8eafc 100644 --- a/src/styles/_pages.scss +++ b/src/styles/_pages.scss @@ -1395,6 +1395,7 @@ background-position: 99% 50%; background-repeat: no-repeat; padding: 16px; + border-radius: 8px; &__title { color: #FFBD00; @@ -1421,6 +1422,16 @@ background: #C4DFE6; } + &-item { + display: grid; + gap: 8px; + grid-template-columns: 80px 1fr 1fr 32px; + + &__single { + grid-template-columns: 80px 1fr 1fr; + } + } + &__inner{ display: flex; flex-direction: column; @@ -1437,6 +1448,7 @@ &__wrap { display: flex; gap: 8px; + flex-direction: column; .btn-cancel, .btn-edit { diff --git a/src/styles/view/_select.scss b/src/styles/view/_select.scss index e124a3c..a68558e 100644 --- a/src/styles/view/_select.scss +++ b/src/styles/view/_select.scss @@ -53,8 +53,15 @@ position: absolute; left: 16px; top: 15px; + right: 22px; z-index: 1; transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } } } @@ -110,7 +117,14 @@ position: absolute; left: 16px; top: 15px; + right: 22px; z-index: 1; transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } } } diff --git a/src/styles/view/_timepicker.scss b/src/styles/view/_timepicker.scss new file mode 100644 index 0000000..6d5cda3 --- /dev/null +++ b/src/styles/view/_timepicker.scss @@ -0,0 +1,59 @@ +.b-timepicker { + width: 100% !important; + height: 54px !important; + + &.ant-picker-filled { + background: transparent !important; + z-index: 1; + padding-top: 22px !important; + + &:hover { + border-color: #2c7873 !important; + } + + .ant-picker-input { + input { + font-size: 15px !important; + } + } + } + + .ant-picker-suffix { + margin-top: -20px; + } + + &-wrap { + position: relative; + width: 100%; + background-color: #F8F8F7; + border-radius: 8px; + + &.b-timepicker__active .b-timepicker-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; + right: 22px; + z-index: 0; + transition: all .1s ease; + overflow: hidden; + text-overflow: ellipsis; + + span { + white-space: nowrap; + } + } +} diff --git a/src/styles/view/style.scss b/src/styles/view/style.scss index 42b0936..b626bbd 100644 --- a/src/styles/view/style.scss +++ b/src/styles/view/style.scss @@ -8,3 +8,4 @@ @import "_buttons.scss"; @import "_practice.scss"; @import "_collapse.scss"; +@import "_timepicker.scss"; diff --git a/src/types/profile.ts b/src/types/profile.ts index 6da1047..3d30c4e 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -1,8 +1,8 @@ import { UploadFile } from 'antd'; -import {EducationDTO} from "./education"; -import {ExpertsTags} from "./tags"; -import {PracticeDTO} from "./practice"; -import {ScheduleDTO} from "./schedule"; +import { EducationDTO } from './education'; +import { ExpertsTags } from './tags'; +import { PracticeDTO } from './practice'; +import { ScheduleDTO } from './schedule'; export type ProfileData = { username?: string; diff --git a/src/types/schedule.ts b/src/types/schedule.ts index fc18ca9..4e56926 100644 --- a/src/types/schedule.ts +++ b/src/types/schedule.ts @@ -3,8 +3,15 @@ export type WorkingTime = { startTimeUtc?: number, endDayOfWeekUtc?: string, endTimeUtc?: number -} +}; export interface ScheduleDTO { workingTimes?: WorkingTime[] } + +export type MapWorkingTime = { + startDay?: string; + startTimeMin?: number; + endDay?: string; + endTimeMin?: number; +}; diff --git a/src/utils/time.ts b/src/utils/time.ts index 9657e3a..86f0cf9 100644 --- a/src/utils/time.ts +++ b/src/utils/time.ts @@ -1,16 +1,24 @@ -import dayjs from 'dayjs'; -import { WorkingTime } from '../types/schedule'; +import { MapWorkingTime, WorkingTime } from '../types/schedule'; -const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; +export const WEEK_DAY = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday']; const MAX_DAY_TIME = 24 * 60; // min -export const getCurrentTime = (data: WorkingTime) => { +const addWeekDay = (weekDay?: string): string => { + const ind = weekDay ? WEEK_DAY.indexOf(weekDay) + 1 : 0; + return WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; +} + +const subWeekDay = (weekDay?: string): string => { + const ind = weekDay ? WEEK_DAY.indexOf(weekDay) - 1 : 0; + return WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; +} + +export const getCurrentTime = (data: WorkingTime, timeZone: string): MapWorkingTime => { let startDay = data.startDayOfWeekUtc; let endDay = data.endDayOfWeekUtc; - const currentTimeZone = dayjs().format('Z'); const startUtc = data.startTimeUtc / (1000 * 1000 * 60); const endUtc = data.endTimeUtc / (1000 * 1000 * 60); - const matches = currentTimeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/); + const matches = timeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; const sign = matches[1]; const h = matches[2]; const m = matches[3]; @@ -29,34 +37,186 @@ export const getCurrentTime = (data: WorkingTime) => { endMin = endUtc - (Number(h) * 60) - Number(m); } - if (startMin > MAX_DAY_TIME) { - startMin = startMin - MAX_DAY_TIME; - const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0; - startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + if (startMin >= MAX_DAY_TIME) { + startMin = 0; + // startMin = startMin - MAX_DAY_TIME; + // const ind = startDay ? WEEK_DAY.indexOf(startDay) + 1 : 0; + // startDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; } - if (endMin > MAX_DAY_TIME) { - endMin = endMin - MAX_DAY_TIME; - const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0; - endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; + if (endMin >= MAX_DAY_TIME) { + endMin = 0; + // endMin = endMin - MAX_DAY_TIME; + // const ind = endDay ? WEEK_DAY.indexOf(endDay) + 1 : 0; + // endDay = WEEK_DAY[ind >= WEEK_DAY.length ? 0 : ind]; } if (startMin <= 0) { - startMin = MAX_DAY_TIME - startMin; - const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0; - startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + startMin = 0; + // startMin = MAX_DAY_TIME - startMin; + // const ind = startDay ? WEEK_DAY.indexOf(startDay) - 1 : 0; + // startDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; } if (endMin <= 0) { - endMin = MAX_DAY_TIME - endMin; - const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0; - endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; + endMin = 0; + // endMin = MAX_DAY_TIME - endMin; + // const ind = endDay ? WEEK_DAY.indexOf(endDay) - 1 : 0; + // endDay = WEEK_DAY[ind < 0 ? WEEK_DAY.length - 1 : ind]; } return { startDay, - startTime: `${(startMin - startMin % 60)/60}:${startMin % 60 || '00'}`, + startTimeMin: startMin, endDay: endDay, - endTime: `${(endMin - endMin % 60)/60}:${endMin % 60 || '00'}` + endTimeMin: endMin } }; + +export const getTimeString = (min: number) => { + const timeH = `${(min - min % 60)/60}`; + + return `${timeH.length === 1 ? `0${timeH}` : timeH}:${min % 60 || '00'}`; +} + +export const formattedSchedule = (workingTimes: WorkingTime[], timeZone: string): MapWorkingTime[] => ( + workingTimes.map((time) => getCurrentTime(time, timeZone)) || [] +); + +export const getNewTime = (time: string): number => { + const timeArr = time.split(':'); + + return Number(timeArr[0]) * 60 + Number(timeArr[1]); +}; + +export const getTimeZoneOffset = (oldTime: string, newTime: string): { sign: string, offset: number } => { + // старая таймзона + const matches1 = oldTime.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign1 = matches1[1]; + const min1 = Number(matches1[2]) * 60 + Number(matches1[3]); + // новая таймзона + const matches2 = newTime.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign2 = matches2[1]; + const min2 = Number(matches2[2]) * 60 + Number(matches2[3]); + + if (sign1 === '+' && sign2 === '+') { + if (min1 < min2) { + return { + sign: '+', + offset: min2 - min1 + } + } else { + return { + sign: '-', + offset: min1 - min2 + } + } + } + + if (sign1 === '-' && sign2 === '-') { + if (min1 < min2) { + return { + sign: '-', + offset: min2 - min1 + } + } else { + return { + sign: '+', + offset: min1 - min2 + } + } + } + + if (sign1 === '+' && sign2 === '-') { + return { + sign: '-', + offset: min1 + min2 + } + } + + if (sign1 === '-' && sign2 === '+') { + return { + sign: '+', + offset: min1 + min2 + } + } +} + +export const formattedTimeByOffset = (workTime: MapWorkingTime, objOffset: { sign: string, offset: number }): MapWorkingTime => { + if (objOffset.sign === '+') { + const resStartMin = workTime.startTimeMin + objOffset.offset; + const resEndMin = workTime.endTimeMin ? workTime.endTimeMin + objOffset.offset : workTime.endTimeMin; + + return { + ...workTime, + startTimeMin: resStartMin >= MAX_DAY_TIME ? 0 : resStartMin, + endTimeMin: resEndMin >= MAX_DAY_TIME ? 0 : resEndMin + } + } + + if (objOffset.sign === '-') { + const resStartMin = workTime.startTimeMin - objOffset.offset; + const resEndMin = workTime.endTimeMin ? workTime.endTimeMin - objOffset.offset : workTime.endTimeMin; + + return { + ...workTime, + startTimeMin: resStartMin < 0 ? 0 : resStartMin, + endTimeMin: resEndMin < 0 ? 0 : resEndMin + } + } + + return workTime; +}; + +const getResultTime = (data: MapWorkingTime, timeZone: string): WorkingTime => { + let startDayOfWeekUtc = data.startDay; + let endDayOfWeekUtc = data.startDay; + const matches = timeZone.match(/(\+|-)([0-9]{2}):([0-9]{2})/) || []; + const sign = matches[1]; + const offset = (Number(matches[2]) * 60) + Number(matches[3]); + let startTime = data.startTimeMin; + let endTime = data.endTimeMin === 0 ? MAX_DAY_TIME : data.endTimeMin; + + if (sign === '+') { + startTime = startTime - offset; + endTime = endTime - offset; + } + + if (sign === '-') { + startTime = startTime + offset; + endTime = endTime + offset; + } + + if (startTime >= MAX_DAY_TIME) { + startTime = startTime - MAX_DAY_TIME; + startDayOfWeekUtc = addWeekDay(startDayOfWeekUtc); + } + + if (endTime >= MAX_DAY_TIME) { + endTime = endTime - MAX_DAY_TIME; + endDayOfWeekUtc = addWeekDay(endDayOfWeekUtc); + } + + if (startTime < 0) { + startTime = MAX_DAY_TIME - Math.abs(startTime || 0); + startDayOfWeekUtc = subWeekDay(startDayOfWeekUtc); + } + + if (endTime < 0) { + endTime = MAX_DAY_TIME - Math.abs(endTime || 0); + endDayOfWeekUtc = subWeekDay(endDayOfWeekUtc); + } + + return { + startDayOfWeekUtc, + startTimeUtc: startTime * 1000 * 1000 * 60, + endDayOfWeekUtc, + endTimeUtc: endTime * 1000 * 1000 * 60 + }; +} + +export const formattedWorkList = (workingList: MapWorkingTime[], timeZone: string): WorkingTime[] => ( + workingList + .filter(({ startTimeMin, endTimeMin }) => !(!startTimeMin && !endTimeMin)) + .map((time) => getResultTime(time, timeZone)) || [] +); From 76ffdc4094e9b9848edd02c786fe4f5697e383f4 Mon Sep 17 00:00:00 2001 From: SD Date: Fri, 6 Sep 2024 14:40:19 +0400 Subject: [PATCH 20/23] 0.0.2 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 79eeae5..0aaeb36 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bbuddy-ui", - "version": "0.0.1", + "version": "0.0.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bbuddy-ui", - "version": "0.0.1", + "version": "0.0.2", "dependencies": { "@ant-design/cssinjs": "^1.18.1", "@ant-design/icons": "^5.2.6", diff --git a/package.json b/package.json index 4b6b281..2985e91 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bbuddy-ui", - "version": "0.0.1", + "version": "0.0.2", "private": true, "scripts": { "dev": "next dev -p 4200", From 3345a533d29db6aa9a95dc632305726f33abdf95 Mon Sep 17 00:00:00 2001 From: SD Date: Fri, 6 Sep 2024 15:22:55 +0400 Subject: [PATCH 21/23] feat: add appVersion in browser --- next.config.js | 4 ++++ src/app/[locale]/layout.tsx | 7 +++++-- src/components/Page/AppConfig.tsx | 11 +++++++++++ src/components/Page/index.ts | 1 + 4 files changed, 21 insertions(+), 2 deletions(-) create mode 100644 src/components/Page/AppConfig.tsx diff --git a/next.config.js b/next.config.js index 8eedf80..ccf41ec 100644 --- a/next.config.js +++ b/next.config.js @@ -1,6 +1,7 @@ // @ts-check const withNextIntl = require('next-intl/plugin')(); const path = require('path'); +const json = require('./package.json'); /** @type {import('next').NextConfig} */ const nextConfig = { @@ -14,6 +15,9 @@ const nextConfig = { sassOptions: { includePaths: [path.join(__dirname, 'styles')], }, + env: { + version: json.version + }, typescript: { // !! WARN !! // Dangerously allow production builds to successfully complete even if diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx index 520e6ae..3ab1d50 100644 --- a/src/app/[locale]/layout.tsx +++ b/src/app/[locale]/layout.tsx @@ -1,4 +1,4 @@ -import React, { ReactNode } from 'react'; +import React, { ReactNode, Suspense } from 'react'; import { Metadata } from 'next'; import { unstable_setRequestLocale } from 'next-intl/server'; import { notFound } from 'next/navigation'; @@ -6,7 +6,7 @@ import { ConfigProvider } from 'antd'; import { AntdRegistry } from '@ant-design/nextjs-registry'; import theme from '../../constants/theme'; import { ALLOWED_LOCALES } from '../../constants/locale'; -import { Header, Footer } from '../../components/Page'; +import { Header, Footer, AppConfig } from '../../components/Page'; type LayoutProps = { children: ReactNode; @@ -30,6 +30,9 @@ export default function LocaleLayout({ children, params: { locale } }: LayoutPro
    + + +
    {children} diff --git a/src/components/Page/AppConfig.tsx b/src/components/Page/AppConfig.tsx new file mode 100644 index 0000000..56a44da --- /dev/null +++ b/src/components/Page/AppConfig.tsx @@ -0,0 +1,11 @@ +'use client' + +import { useEffect } from 'react'; + +export const AppConfig = () => { + useEffect(() => { + console.log('AppVersion', process.env.version); + }, []); + + return null; +}; diff --git a/src/components/Page/index.ts b/src/components/Page/index.ts index c732478..6059d1b 100644 --- a/src/components/Page/index.ts +++ b/src/components/Page/index.ts @@ -1,3 +1,4 @@ export * from './Header'; export * from './Footer'; export * from './GeneralTopSection'; +export * from './AppConfig'; From 3ed78c0e45875bc6b3969d43f87eb203f93d78a5 Mon Sep 17 00:00:00 2001 From: SD Date: Tue, 10 Sep 2024 20:49:19 +0400 Subject: [PATCH 22/23] fix: fix main filter point, fix save profile pictures --- next.config.js | 3 +- src/components/Account/ProfileSettings.tsx | 50 +++++++++++++--------- src/components/Experts/Filter.tsx | 1 + src/styles/_form.scss | 3 +- src/types/profile.ts | 3 +- 5 files changed, 35 insertions(+), 25 deletions(-) diff --git a/next.config.js b/next.config.js index ccf41ec..0cfa5f5 100644 --- a/next.config.js +++ b/next.config.js @@ -34,8 +34,7 @@ const nextConfig = { }, // output: 'standalone', poweredByHeader: false, - productionBrowserSourceMaps: true, - trailingSlash: true + productionBrowserSourceMaps: true }; module.exports = withNextIntl(nextConfig); diff --git a/src/components/Account/ProfileSettings.tsx b/src/components/Account/ProfileSettings.tsx index 21da0a7..cfbb04b 100644 --- a/src/components/Account/ProfileSettings.tsx +++ b/src/components/Account/ProfileSettings.tsx @@ -15,6 +15,7 @@ import { OutlinedButton } from '../view/OutlinedButton'; import {FilledButton, FilledSquareButton, FilledYellowButton} from '../view/FilledButton'; import { DeleteAccountModal } from '../Modals/DeleteAccountModal'; import { Loader } from '../view/Loader'; +import {ButtonProps} from "antd/es/button/button"; type ProfileSettingsProps = { locale: string; @@ -40,6 +41,20 @@ export const ProfileSettings: FC = ({ locale }) => { } }, [profileSettings]); + const onSave = (newProfile: ProfileRequest) => { + setSaveLoading(true); + save(newProfile) + .then(() => { + fetchProfileSettings(); + }) + .catch(() => { + message.error('Не удалось сохранить изменения'); + }) + .finally(() => { + setSaveLoading(false); + }) + } + const onSaveProfile = () => { form.validateFields() .then(({ login, surname, username }) => { @@ -56,27 +71,18 @@ export const ProfileSettings: FC = ({ locale }) => { }; if (photo) { - console.log(photo); - const formData = new FormData(); - formData.append('file', photo as FileType); + const reader = new FileReader(); + reader.readAsDataURL(photo as File); + reader.onloadend = () => { + const newReg = new RegExp('data:image/(png|jpg|jpeg);base64,') + newProfile.faceImage = reader.result.replace(newReg, ''); + newProfile.isFaceImageKeepExisting = false; - newProfile.faceImage = `[${(photo as File).arrayBuffer()}]`; - newProfile.isFaceImageKeepExisting = false; + onSave(newProfile); + } + } else { + onSave(newProfile); } - - console.log(newProfile); - - setSaveLoading(true); - save(newProfile) - .then(() => { - fetchProfileSettings(); - }) - .catch(() => { - message.error('Не удалось сохранить изменения'); - }) - .finally(() => { - setSaveLoading(false); - }) }) } @@ -103,6 +109,10 @@ export const ProfileSettings: FC = ({ locale }) => { modalTitle="Редактировать" modalOk="Сохранить" modalCancel="Отмена" + modalProps={{ + okButtonProps: { className: 'b-button__filled_yellow' }, + cancelButtonProps: { className: 'b-button__outlined' } + }} beforeCrop={beforeCrop} > = ({ locale }) => { url: profileSettings.faceImageUrl } ] : undefined} - accept=".jpg,.jpeg,.png,.gif" + accept=".jpg,.jpeg,.png" beforeUpload={beforeUpload} multiple={false} showUploadList={false} diff --git a/src/components/Experts/Filter.tsx b/src/components/Experts/Filter.tsx index debb1d1..846dd34 100644 --- a/src/components/Experts/Filter.tsx +++ b/src/components/Experts/Filter.tsx @@ -114,6 +114,7 @@ export const ExpertsFilter = ({ ...getObjectByAdditionalFilter(searchParams) }; const search = getSearchParamsString(newFilter); + console.log('basePath', basePath); router.push(search ? `${basePath}?${search}#filter` : `${basePath}#filter`); diff --git a/src/styles/_form.scss b/src/styles/_form.scss index db377ea..6e00e77 100644 --- a/src/styles/_form.scss +++ b/src/styles/_form.scss @@ -109,10 +109,11 @@ textarea { } .user-avatar { - display: flex; + display: grid; gap: 16px; align-items: center; margin-bottom: 4px; + grid-template-columns: 100px auto; &__edit { position: relative; diff --git a/src/types/profile.ts b/src/types/profile.ts index 3d30c4e..a429ca0 100644 --- a/src/types/profile.ts +++ b/src/types/profile.ts @@ -1,4 +1,3 @@ -import { UploadFile } from 'antd'; import { EducationDTO } from './education'; import { ExpertsTags } from './tags'; import { PracticeDTO } from './practice'; @@ -28,7 +27,7 @@ export type ProfileRequest = { languagesLinks?: { languageId: number }[]; username?: string; surname?: string; - faceImage?: UploadFile; + faceImage?: any; isFaceImageKeepExisting?: boolean; phone?: string; }; From 4b429c8655ecb80c456f444586c44d4258f6728d Mon Sep 17 00:00:00 2001 From: SD Date: Tue, 10 Sep 2024 20:49:39 +0400 Subject: [PATCH 23/23] 0.0.3 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 0aaeb36..87a7bdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "bbuddy-ui", - "version": "0.0.2", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "bbuddy-ui", - "version": "0.0.2", + "version": "0.0.3", "dependencies": { "@ant-design/cssinjs": "^1.18.1", "@ant-design/icons": "^5.2.6", diff --git a/package.json b/package.json index 2985e91..272bce1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "bbuddy-ui", - "version": "0.0.2", + "version": "0.0.3", "private": true, "scripts": { "dev": "next dev -p 4200",