diff --git a/src/components/Modals/ScheduleModal.tsx b/src/components/Modals/ScheduleModal.tsx index 0ab0947..93ede9c 100644 --- a/src/components/Modals/ScheduleModal.tsx +++ b/src/components/Modals/ScheduleModal.tsx @@ -2,8 +2,8 @@ import React, {FC, useEffect, useState} from 'react'; import classNames from 'classnames'; -import { Modal, Menu, Calendar, Radio, Button, Input, message } from 'antd'; -import type { CalendarProps, RadioChangeEvent, MenuProps } from 'antd'; +import { Modal, Menu, Calendar, Radio, Button, Input, message, Form } from 'antd'; +import type { CalendarProps, MenuProps } from 'antd'; import { ArrowLeftOutlined } from '@ant-design/icons'; import { CloseOutlined } from '@ant-design/icons'; import locale_ru from 'antd/lib/calendar/locale/ru_RU'; @@ -57,7 +57,7 @@ const getLocale = (locale: string) => { return locale_es; default: return locale_en; - }; + } } return locale_en; @@ -83,12 +83,12 @@ export const ScheduleModal: FC = ({ checkSession, }) => { const [selectDate, setSelectDate] = useState(dayjs()); - const [dates, setDates] = useState(); + const [dates, setDates] = useState | undefined>(); const [tags, setTags] = useState(); - const [sessionData, setSessionData] = useState({ coachId: +expertId }); const [rawScheduler, setRawScheduler] = useState(null); const [isPayLoading, setIsPayLoading] = useState(false); const [sessionId, setSessionId] = useState(''); + const [form] = Form.useForm<{ clientComment?: string, startAtUtc?: string, tagId?: number }>(); dayjs.locale(locale); @@ -117,7 +117,6 @@ export const ScheduleModal: FC = ({ useEffect(()=> { if (open && mode !== 'pay') { - setSessionData({ coachId: +expertId }); getSchedulerByExpertId(expertId as string, locale as string) .then((data) => { setRawScheduler(data); @@ -126,6 +125,10 @@ export const ScheduleModal: FC = ({ console.log(err); }); } + + if (!open) { + form.resetFields(); + } }, [open]); useEffect(() => { @@ -158,10 +161,6 @@ export const ScheduleModal: FC = ({ const disabledDate = (currentDate: Dayjs) => !dates || !dates[currentDate.format('YYYY-MM-DD')]; - const onChangeTimeSlot = (e: RadioChangeEvent) => setSessionData({ ...sessionData, startAtUtc: e.target.value.startTime }); - - const onChangeTag = (tagId: number) => setSessionData({ ...sessionData, tagId }); - const cellRender: CalendarProps['fullCellRender'] = (date, info) => { const isWeekend = date.day() === 6 || date.day() === 0; return React.cloneElement(info.originNode, { @@ -181,6 +180,13 @@ export const ScheduleModal: FC = ({ }); }; + const onValidate = () => { + form.validateFields() + .then((values) => { + checkSession({ coachId: +expertId, ...values }); + }) + } + return ( = ({ {selectDate.locale(locale).format('DD MMMM YYYY')} -
- {tags && ( - ({ value: id, label: name }))} - onChange={onChangeTag} +
+
+ {tags && ( + + ({value: id, label: name}))} + /> + + )} +
+
+ + + {dates && dates[selectDate.format('YYYY-MM-DD')].map((el: any) => ( + + {dayjs(el.startTime).format('HH:mm')} - {dayjs(el.endTime).format('HH:mm')} + ) + )} + + +
+ + - )} -
-
- - {dates[selectDate.format('YYYY-MM-DD')].map((el: any) => { - return ({dayjs(el.startTime).format('HH:mm')} - {dayjs(el.endTime).format('HH:mm')}) - })} - -
-
- setSessionData({ ...sessionData, clientComment: e.target.value })} - /> -
+ + diff --git a/src/components/Modals/ScheduleModalResult.tsx b/src/components/Modals/ScheduleModalResult.tsx index ff9f5e6..58d97c0 100644 --- a/src/components/Modals/ScheduleModalResult.tsx +++ b/src/components/Modals/ScheduleModalResult.tsx @@ -45,7 +45,7 @@ export const ScheduleModalResult = ({ locale }: { locale: string }) => { const onClose = () => { const { origin, pathname } = window?.location || {}; - + router.push(`${origin}${pathname}`); setPaymentStatus(undefined); setSession(undefined); diff --git a/src/components/stripe/StripeElementsForm.tsx b/src/components/stripe/StripeElementsForm.tsx index fc51dc6..004653c 100644 --- a/src/components/stripe/StripeElementsForm.tsx +++ b/src/components/stripe/StripeElementsForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { FC, useEffect, useState } from 'react'; +import React, { FC, useEffect, useState } from 'react'; import type { StripeError } from '@stripe/stripe-js'; import { useStripe, @@ -8,11 +8,12 @@ import { PaymentElement, Elements, } from '@stripe/react-stripe-js'; -import { Form, Button } from 'antd'; +import { Form, Button, message } from 'antd'; import getStripe from '../../utils/get-stripe'; import { createPaymentIntent} from '../../actions/stripe'; import { Payment } from '../../types/payment'; import { i18nText } from '../../i18nKeys'; +import { WithError } from '../view/WithError'; type PaymentFormProps = { amount: number, @@ -20,39 +21,37 @@ type PaymentFormProps = { locale: string } +type PaymentInfo = 'initial' | 'error' | 'processing' | 'requires_payment_method' | 'requires_confirmation' | 'requires_action' | 'succeeded'; + +const PaymentStatus = ({ status }: { status?: PaymentInfo }) => { + switch (status) { + case 'processing': + case 'requires_payment_method': + case 'requires_confirmation': + return

Processing...

; + + case 'requires_action': + return

Authenticating...

; + + case 'succeeded': + return

Payment Succeeded

; + + default: + return null; + } +}; + export const CheckoutForm: FC = ({ amount, sessionId, locale }) => { const [form] = Form.useForm(); const formAmount = Form.useWatch('amount', form); const [paymentType, setPaymentType] = useState(''); const [payment, setPayment] = useState<{ - status: "initial" | "processing" | "error"; - }>({ status: "initial" }); - const [errorMessage, setErrorMessage] = useState(''); + status: PaymentInfo + }>({ status: 'initial' }); + const [errorData, setErrorData] = 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": - console.log('errorMessage', errorMessage); - return null; - - default: - return null; - } - }; - useEffect(() => { elements?.update({ amount: formAmount * 100 }); }, [formAmount]); @@ -61,13 +60,15 @@ export const CheckoutForm: FC = ({ amount, sessionId, locale } try { if (!elements || !stripe) return; + setErrorData(undefined); setPayment({ status: "processing" }); const { error: submitError } = await elements.submit(); if (submitError) { - setPayment({ status: "error" }); - setErrorMessage(submitError.message ?? "An unknown error occurred"); + if (submitError.message) { + message.error(submitError.message); + } return; } @@ -91,40 +92,45 @@ export const CheckoutForm: FC = ({ amount, sessionId, locale } }); if (confirmError) { - setPayment({ status: "error" }); - setErrorMessage(confirmError.message ?? "An unknown error occurred"); + setErrorData({ + title: i18nText('errorPayment', locale), + message: confirmError.message ?? 'An unknown error occurred' + }); } } catch (err) { const { message } = err as StripeError; - - setPayment({ status: "error" }); - setErrorMessage(message ?? "An unknown error occurred"); + setErrorData({ + title: i18nText('errorPayment', locale), + message: message ?? 'An unknown error occurred' + }); } }; return ( -
-
- { - setPaymentType(e.value.type); - }} - /> -
-
- -
- -
+ +
+
+ { + setPaymentType(e.value.type); + }} + /> +
+
+ +
+ +
+
); } @@ -143,7 +149,7 @@ export const StripeElementsForm: FC = ({ amount, sessionId, lo colorPrimary: '#66A5AD', colorBackground: '#F8F8F7', colorText: '#000', - colorDanger: '#D93E5C', + colorDanger: '#ff4d4f', focusBoxShadow: 'none', borderRadius: '8px' }, diff --git a/src/components/view/WithError.tsx b/src/components/view/WithError.tsx index 454a45f..f7b4a3f 100644 --- a/src/components/view/WithError.tsx +++ b/src/components/view/WithError.tsx @@ -16,8 +16,8 @@ export const WithError: FC = ({ return ( Refresh page diff --git a/src/i18nKeys/de.ts b/src/i18nKeys/de.ts index 0207b55..b34ec39 100644 --- a/src/i18nKeys/de.ts +++ b/src/i18nKeys/de.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Führungserfahrung', pay: 'Zahlung', sessionWishes: 'Schreiben Sie Ihre Wünsche zur Sitzung', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Erfolgreiche Zahlung', + errorPayment: 'Zahlungsfehler', 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 b4823ea..e8f55da 100644 --- a/src/i18nKeys/en.ts +++ b/src/i18nKeys/en.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Managerial Experience', pay: 'Pay', sessionWishes: 'Write your wishes about the session', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Successful Payment', + errorPayment: 'Payment Error', 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 263078d..16b9dfd 100644 --- a/src/i18nKeys/es.ts +++ b/src/i18nKeys/es.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Experiencia de dirección', pay: 'Pago', sessionWishes: 'Escribe tus deseos sobre la sesión', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Pago Exitoso', + errorPayment: 'Error de Pago', 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 6ef4be4..b4ee9e6 100644 --- a/src/i18nKeys/fr.ts +++ b/src/i18nKeys/fr.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Expérience en gestion', pay: 'Paiement', sessionWishes: 'Écrivez vos souhaits concernant la session', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Paiement Réussi', + errorPayment: 'Erreur de Paiement', 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 d2b0fdd..5633747 100644 --- a/src/i18nKeys/it.ts +++ b/src/i18nKeys/it.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Esperienza manageriale', pay: 'Pagamento', sessionWishes: 'Scrivi i tuoi desideri riguardo alla sessione', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Pagamento Riuscito', + errorPayment: 'Errore di Pagamento', 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 0a795f8..e8252b6 100644 --- a/src/i18nKeys/ru.ts +++ b/src/i18nKeys/ru.ts @@ -148,8 +148,8 @@ export default { mExperiences: 'Управленческий опыт', pay: 'Оплата', sessionWishes: 'Напишите свои пожелания по поводу сессии', - successPayment: 'Success', - errorPayment: 'Error', + successPayment: 'Успешная оплата', + errorPayment: 'Ошибка оплаты', errors: { invalidEmail: 'Адрес электронной почты недействителен', emptyEmail: 'Пожалуйста, введите ваш E-mail', diff --git a/src/styles/view/_radio.scss b/src/styles/view/_radio.scss new file mode 100644 index 0000000..8e3bf9b --- /dev/null +++ b/src/styles/view/_radio.scss @@ -0,0 +1,3 @@ +.ant-form-item-has-error .ant-radio-inner { + border-color: #ff4d4f !important; +} \ No newline at end of file diff --git a/src/styles/view/_select.scss b/src/styles/view/_select.scss index 3feaf85..2bd47c1 100644 --- a/src/styles/view/_select.scss +++ b/src/styles/view/_select.scss @@ -17,6 +17,12 @@ } } + &.ant-select-status-error { + .ant-select-selector { + border-color: #ff4d4f !important; + } + } + .ant-select-selection-overflow-item { margin-right: 4px; } @@ -88,6 +94,12 @@ } } + &.ant-select-status-error { + .ant-select-selector { + border-color: #ff4d4f !important; + } + } + .ant-select-arrow { color: #2c7873 !important; } diff --git a/src/styles/view/style.scss b/src/styles/view/style.scss index 569d2c4..09086f3 100644 --- a/src/styles/view/style.scss +++ b/src/styles/view/style.scss @@ -11,3 +11,4 @@ @import "_timepicker.scss"; @import "_calendar.scss"; @import "_schedule.scss"; +@import "_radio.scss";