From ef239432d5953fec20f4bbcb0511119e24450815 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=94=D0=B5=D0=BD=D0=B8=D1=81=20=D0=9A=D0=B0=D1=82=D0=B0?= =?UTF-8?q?=D0=B5=D0=B2?= Date: Tue, 9 Apr 2024 16:46:50 +0000 Subject: [PATCH] add short path for advisor chat --- src/api/api.ts | 4 +- src/api/resources/Products.ts | 29 +++ src/api/resources/SinglePayment.ts | 14 +- src/api/resources/index.ts | 1 + src/components/App/index.tsx | 126 +++++++++- src/components/BirthdayPage/index.tsx | 10 +- src/components/BirthtimePage/index.tsx | 48 ++-- src/components/PaymentPage/results/index.tsx | 23 +- .../pages/AddConsultation/index.tsx | 3 +- .../pages/AddReport/index.tsx | 3 +- .../pages/UnlimitedReadings/index.tsx | 3 +- .../components/ChatHeader/index.tsx | 10 +- src/components/pages/AdvisorChat/index.tsx | 22 +- src/components/pages/Advisors/index.tsx | 2 +- src/components/pages/BirthPlacePage/index.tsx | 48 ++++ .../pages/BirthPlacePage/styles.module.css | 28 +++ src/components/pages/Gender/index.tsx | 3 + src/components/pages/LoadingPage/index.tsx | 12 + .../pages/LoadingPage/styles.module.css | 8 + .../ResultPayment/FailPaymentPage/index.tsx | 7 +- .../SuccessPaymentPage/index.tsx | 22 +- .../pages/PaymentWithEmailPage/index.tsx | 226 ++++++++---------- src/data/products.ts | 35 +++ src/hooks/payment/useSinglePayment.ts | 156 ++++++++++++ src/hooks/useSchemeColorByElement.tsx | 2 - src/routes.ts | 30 ++- 26 files changed, 686 insertions(+), 189 deletions(-) create mode 100644 src/api/resources/Products.ts create mode 100644 src/components/pages/BirthPlacePage/index.tsx create mode 100644 src/components/pages/BirthPlacePage/styles.module.css create mode 100644 src/components/pages/LoadingPage/index.tsx create mode 100644 src/components/pages/LoadingPage/styles.module.css create mode 100644 src/data/products.ts create mode 100644 src/hooks/payment/useSinglePayment.ts diff --git a/src/api/api.ts b/src/api/api.ts index 435efe1..ca306a3 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -27,6 +27,7 @@ import { Assistants, OpenAI, SinglePayment, + Products, } from './resources' const api = { @@ -69,7 +70,8 @@ const api = { getListRuns: createMethod(OpenAI.createRequest), // Single payment getSinglePaymentProducts: createMethod(SinglePayment.createRequestGet), - createSinglePayment: createMethod(SinglePayment.createRequestPost), + createSinglePayment: createMethod(SinglePayment.createRequestPost), + checkProductPurchased: createMethod(Products.createRequest), } export type ApiContextValue = typeof api diff --git a/src/api/resources/Products.ts b/src/api/resources/Products.ts new file mode 100644 index 0000000..a75100a --- /dev/null +++ b/src/api/resources/Products.ts @@ -0,0 +1,29 @@ +import routes from "@/routes"; +import { getAuthHeaders } from "../utils"; + +interface Payload { + token: string; +} + +export interface PayloadGet extends Payload { + productKey: string; + email: string; +} + +interface ResponseGetSuccess { + status: string; + type: string; + active: boolean; +} + +interface ResponseGetError { + status: string; + message: string; +} + +export type ResponseGet = ResponseGetSuccess | ResponseGetError; + +export const createRequest = ({ token, productKey, email }: PayloadGet): Request => { + const url = new URL(routes.server.dApiCheckProductPurchased(productKey, email)); + return new Request(url, { method: "GET", headers: getAuthHeaders(token) }); +}; diff --git a/src/api/resources/SinglePayment.ts b/src/api/resources/SinglePayment.ts index 991d878..f99a6ff 100644 --- a/src/api/resources/SinglePayment.ts +++ b/src/api/resources/SinglePayment.ts @@ -38,7 +38,7 @@ export interface ResponseGet { currency: string; } -export interface ResponsePost { +interface ResponsePostNewPaymentData { paymentIntent: { status: string; data: { @@ -60,13 +60,23 @@ export interface ResponsePost { }; } -export interface ResponsePostExistPaymentData { +interface ResponsePostExistPaymentData { payment: { status: string; invoiceId: string; }; } +interface ResponsePostError { + status: string; + message: string; +} + +export type ResponsePost = + | ResponsePostNewPaymentData + | ResponsePostExistPaymentData + | ResponsePostError; + export const createRequestPost = ({ data, token }: PayloadPost): Request => { const url = new URL(routes.server.dApiPaymentCheckout()); const body = JSON.stringify(data); diff --git a/src/api/resources/index.ts b/src/api/resources/index.ts index 6c11bf3..e90c5b2 100644 --- a/src/api/resources/index.ts +++ b/src/api/resources/index.ts @@ -25,3 +25,4 @@ export * as AIRequestsV2 from "./AIRequestsV2"; export * as Assistants from "./Assistants"; export * as OpenAI from "./OpenAI"; export * as SinglePayment from "./SinglePayment"; +export * as Products from "./Products"; diff --git a/src/components/App/index.tsx b/src/components/App/index.tsx index 32c2b4f..4cfb767 100755 --- a/src/components/App/index.tsx +++ b/src/components/App/index.tsx @@ -111,6 +111,8 @@ import SuccessPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/Succ import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage"; import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement"; import GetInformationPartnerPage from "../pages/GetInformationPartner"; +import BirthPlacePage from "../pages/BirthPlacePage"; +import LoadingPage from "../pages/LoadingPage"; const isProduction = import.meta.env.MODE === "production"; @@ -252,13 +254,14 @@ function App(): JSX.Element { return ( }> + } /> {/* Email - Pay - Email */} } /> } /> - } - /> + /> */} } @@ -269,6 +272,68 @@ function App(): JSX.Element { /> {/* Email - Pay - Email */} + {/* Advisor short path */} + + } + > + } + /> + } + /> + } + /> + } + /> + } + /> + } + /> + + + + } + > + + } /> + + + {/* Advisor short path */} + + {/* Single Payment Page Short Path */} + } + > + } /> + + {/* Single Payment Page Short Path */} + {/* Test Routes Start */} } /> }> @@ -692,6 +757,63 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element { ); } +interface ICheckPurchasedSingleProductOutletProps { + productKey: string; + isProductPage: boolean; + failedUrl: string; +} + +function CheckPurchasedSingleProductOutlet({ + productKey, + isProductPage, + failedUrl, +}: ICheckPurchasedSingleProductOutletProps): JSX.Element { + const { user, token } = useAuth(); + const api = useApi(); + + const loadData = useCallback(async () => { + if (!token?.length || !user?.email || !productKey?.length) + return { + status: "error", + error: "Missing params", + }; + try { + const purchased = await api.checkProductPurchased({ + email: user?.email || "", + productKey, + token, + }); + return purchased; + } catch (error) { + console.error(error); + return { + status: "error", + error: "Something went wrong", + }; + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const { data, isPending } = useApiCall(loadData); + + if (!data || isPending) { + return ; + } + + if ( + isProductPage && + (!("active" in data) || !data.active || !token.length || !user?.email) + ) { + return ; + } + + if (!isProductPage && data && "active" in data && data.active) { + return ; + } + + return ; +} + function AuthorizedUserOutlet(): JSX.Element { const status = useSelector(selectors.selectStatus); const { user } = useAuth(); diff --git a/src/components/BirthdayPage/index.tsx b/src/components/BirthdayPage/index.tsx index fabfee9..27f4c3e 100644 --- a/src/components/BirthdayPage/index.tsx +++ b/src/components/BirthdayPage/index.tsx @@ -17,9 +17,13 @@ function BirthdayPage(): JSX.Element { const navigate = useNavigate(); const birthdate = useSelector(selectors.selectBirthdate); const [isDisabled, setIsDisabled] = useState(true); - const nextRoute = window.location.href.includes("/epe/") - ? routes.client.epePayment() - : routes.client.didYouKnow(); + let nextRoute = routes.client.didYouKnow(); + if (window.location.href.includes("/epe/")) { + nextRoute = routes.client.singlePaymentShortPath("moons.pdf.aura"); + } + if (window.location.href.includes("/advisor-chat/")) { + nextRoute = routes.client.advisorChatBirthtime(); + } const handleNext = () => navigate(nextRoute); const handleValid = (birthdate: string) => { dispatch(actions.form.addDate(birthdate)); diff --git a/src/components/BirthtimePage/index.tsx b/src/components/BirthtimePage/index.tsx index 6f4cb8c..ca4782c 100644 --- a/src/components/BirthtimePage/index.tsx +++ b/src/components/BirthtimePage/index.tsx @@ -1,28 +1,34 @@ -import { useNavigate } from "react-router-dom" -import { useTranslation } from 'react-i18next' -import { useDispatch, useSelector } from 'react-redux' -import { actions, selectors } from '@/store' -import { TimePicker } from "../DateTimePicker" -import Title from "../Title" -import MainButton from "../MainButton" -import routes from "@/routes" -import './styles.css' +import { useNavigate } from "react-router-dom"; +import { useTranslation } from "react-i18next"; +import { useDispatch, useSelector } from "react-redux"; +import { actions, selectors } from "@/store"; +import { TimePicker } from "../DateTimePicker"; +import Title from "../Title"; +import MainButton from "../MainButton"; +import routes from "@/routes"; +import "./styles.css"; function BirthtimePage(): JSX.Element { - const { t } = useTranslation() - const dispatch = useDispatch() + const { t } = useTranslation(); + const dispatch = useDispatch(); const navigate = useNavigate(); - const birthtime = useSelector(selectors.selectBirthtime) - const handleNext = () => navigate(routes.client.createProfile()) - const handleChange = (value: string) => dispatch(actions.form.addTime(value)) + const birthtime = useSelector(selectors.selectBirthtime); + let nextRoute = routes.client.createProfile(); + if (window.location.href.includes("/advisor-chat/")) { + nextRoute = routes.client.advisorChatBirthPlace(); + } + const handleNext = () => navigate(nextRoute); + const handleChange = (value: string) => dispatch(actions.form.addTime(value)); return ( -
- {t('born_time_question')} -

{t('nasa_data_using')}

- - {t('next')} +
+ + {t("born_time_question")} + +

{t("nasa_data_using")}

+ + {t("next")}
- ) + ); } -export default BirthtimePage +export default BirthtimePage; diff --git a/src/components/PaymentPage/results/index.tsx b/src/components/PaymentPage/results/index.tsx index df19607..91bca33 100644 --- a/src/components/PaymentPage/results/index.tsx +++ b/src/components/PaymentPage/results/index.tsx @@ -1,5 +1,4 @@ import { useNavigate } from "react-router-dom"; -import routes from "@/routes"; import { useEffect, useState } from "react"; import { useSearchParams } from "react-router-dom"; import { useDispatch } from "react-redux"; @@ -8,6 +7,7 @@ import { actions } from "@/store"; // import { useAuth } from "@/auth"; import styles from "./styles.module.css"; import Loader from "@/components/Loader"; +import { paymentResultPathsOfProducts } from "@/data/products"; function PaymentResultPage(): JSX.Element { // const api = useApi(); @@ -16,7 +16,7 @@ function PaymentResultPage(): JSX.Element { const dispatch = useDispatch(); const [searchParams] = useSearchParams(); const status = searchParams.get("redirect_status"); - const type = searchParams.get("type"); + const redirect_type = searchParams.get("redirect_type"); // const { id } = useParams(); // const requestTimeOutRef = useRef(); const [isLoading] = useState(true); @@ -90,18 +90,19 @@ function PaymentResultPage(): JSX.Element { useEffect(() => { if (status === "succeeded") { dispatch(actions.status.update("subscribed")); - let successPaymentRoute = routes.client.paymentSuccess(); - if (type === "epe") { - successPaymentRoute = routes.client.epeSuccessPayment(); + if ( + !paymentResultPathsOfProducts[redirect_type || ""] || + !redirect_type + ) { + return navigate(paymentResultPathsOfProducts.default.success); } - return navigate(successPaymentRoute); + return navigate(paymentResultPathsOfProducts[redirect_type].success); } - let failPaymentRoute = routes.client.paymentFail(); - if (type === "epe") { - failPaymentRoute = routes.client.epeFailPayment(); + if (!paymentResultPathsOfProducts[redirect_type || ""] || !redirect_type) { + return navigate(paymentResultPathsOfProducts.default.fail); } - return navigate(failPaymentRoute); - }, [navigate, status, dispatch]); + return navigate(paymentResultPathsOfProducts[redirect_type].fail); + }, [navigate, status, dispatch, redirect_type]); return
{isLoading && }
; } diff --git a/src/components/pages/AdditionalPurchases/pages/AddConsultation/index.tsx b/src/components/pages/AdditionalPurchases/pages/AddConsultation/index.tsx index 3d9a002..b194f3e 100644 --- a/src/components/pages/AdditionalPurchases/pages/AddConsultation/index.tsx +++ b/src/components/pages/AdditionalPurchases/pages/AddConsultation/index.tsx @@ -12,7 +12,6 @@ import { selectors } from "@/store"; import { useCallback, useState } from "react"; import { ResponsePost, - ResponsePostExistPaymentData, } from "@/api/resources/SinglePayment"; import { createSinglePayment } from "@/services/singlePayment"; import Modal from "@/components/Modal"; @@ -27,7 +26,7 @@ function AddConsultationPage() { const tokenFromStore = useSelector(selectors.selectToken); const [isLoading, setIsLoading] = useState(false); const [paymentIntent, setPaymentIntent] = useState< - ResponsePost | ResponsePostExistPaymentData | null + ResponsePost | null >(null); const [isError, setIsError] = useState(false); const returnUrl = `${window.location.protocol}//${ diff --git a/src/components/pages/AdditionalPurchases/pages/AddReport/index.tsx b/src/components/pages/AdditionalPurchases/pages/AddReport/index.tsx index e6f08d4..51b55e7 100644 --- a/src/components/pages/AdditionalPurchases/pages/AddReport/index.tsx +++ b/src/components/pages/AdditionalPurchases/pages/AddReport/index.tsx @@ -11,7 +11,6 @@ import PaymentAddress from "../../components/PaymentAddress"; import { createSinglePayment } from "@/services/singlePayment"; import { ResponsePost, - ResponsePostExistPaymentData, } from "@/api/resources/SinglePayment"; import { useAuth } from "@/auth"; import { useSelector } from "react-redux"; @@ -28,7 +27,7 @@ function AddReportPage() { const api = useApi(); const tokenFromStore = useSelector(selectors.selectToken); const [paymentIntent, setPaymentIntent] = useState< - ResponsePost | ResponsePostExistPaymentData | null + ResponsePost | null >(null); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); diff --git a/src/components/pages/AdditionalPurchases/pages/UnlimitedReadings/index.tsx b/src/components/pages/AdditionalPurchases/pages/UnlimitedReadings/index.tsx index 8a0d175..0d02ab3 100644 --- a/src/components/pages/AdditionalPurchases/pages/UnlimitedReadings/index.tsx +++ b/src/components/pages/AdditionalPurchases/pages/UnlimitedReadings/index.tsx @@ -18,7 +18,6 @@ import { createSinglePayment } from "@/services/singlePayment"; import Loader, { LoaderColor } from "@/components/Loader"; import { ResponsePost, - ResponsePostExistPaymentData, } from "@/api/resources/SinglePayment"; import Modal from "@/components/Modal"; import { getPriceCentsToDollars } from "@/services/price"; @@ -42,7 +41,7 @@ function UnlimitedReadingsPage() { const tokenFromStore = useSelector(selectors.selectToken); const [isLoading, setIsLoading] = useState(false); const [paymentIntent, setPaymentIntent] = useState< - ResponsePost | ResponsePostExistPaymentData | null + ResponsePost | null >(null); const [isError, setIsError] = useState(false); const returnUrl = `${window.location.protocol}//${ diff --git a/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx b/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx index f9d31b7..fed1621 100644 --- a/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx +++ b/src/components/pages/AdvisorChat/components/ChatHeader/index.tsx @@ -4,6 +4,7 @@ interface IChatHeaderProps { name: string; avatar: string; classNameContainer?: string; + hasBackButton?: boolean; clickBackButton: () => void; } @@ -11,13 +12,16 @@ function ChatHeader({ name, avatar, classNameContainer = "", + hasBackButton = true, clickBackButton, }: IChatHeaderProps) { return (
-
-
Advisors -
+ {hasBackButton && ( +
+
Advisors +
+ )}
{name} diff --git a/src/components/pages/AdvisorChat/index.tsx b/src/components/pages/AdvisorChat/index.tsx index ae3b5a8..083a220 100644 --- a/src/components/pages/AdvisorChat/index.tsx +++ b/src/components/pages/AdvisorChat/index.tsx @@ -16,6 +16,7 @@ import useDetectScroll from "@smakss/react-scroll-direction"; import { getZodiacSignByDate } from "@/services/zodiac-sign"; function AdvisorChatPage() { + const isPrivateChat = window.location.href.includes("/advisor-chat-private/"); const { id } = useParams(); const api = useApi(); const navigate = useNavigate(); @@ -25,7 +26,9 @@ function AdvisorChatPage() { const birthdate = useSelector(selectors.selectBirthdate); const zodiacSign = getZodiacSignByDate(birthdate); const { username } = useSelector(selectors.selectUser); - const { gender } = useSelector(selectors.selectQuestionnaire); + const { gender, birthtime, birthPlace } = useSelector( + selectors.selectQuestionnaire + ); const [assistant, setAssistant] = useState(); const [messageText, setMessageText] = useState(""); const [textareaRows, setTextareaRows] = useState(1); @@ -88,7 +91,7 @@ function AdvisorChatPage() { idAssistant: string | number ) => { const currentAssistant = aiAssistants.find( - (a) => a.id === Number(idAssistant) + (a) => a.external_id === idAssistant ); return currentAssistant; }; @@ -196,10 +199,17 @@ function AdvisorChatPage() { return run; }; - const createMessage = async (messageText: string, threadId: string) => { + const getContentMessage = (messageText: string) => { const content = `#USER INFO: zodiac sign - ${zodiacSign}; gender - ${gender}; birthdate - ${birthdate}; name - ${ username || "unknown" - };# ${messageText}`; + }; birthtime - ${birthtime || "unknown"}; birthPlace - ${ + birthPlace || "unknown" + }# ${messageText}`; + return content; + }; + + const createMessage = async (messageText: string, threadId: string) => { + const content = getContentMessage(messageText); const message = await api.createMessage({ token: openAiToken, method: "POST", @@ -260,7 +270,8 @@ function AdvisorChatPage() { threadId = assistant.external_chat_id; assistantId = assistant.external_id || ""; } else { - const thread = await createThread(messageText); + const content = getContentMessage(messageText); + const thread = await createThread(content); threadId = thread.id; assistantId = assistant?.external_id || ""; @@ -321,6 +332,7 @@ function AdvisorChatPage() { avatar={assistant?.photo?.th2x || ""} classNameContainer={styles["header-container"]} clickBackButton={() => navigate(-1)} + hasBackButton={!isPrivateChat} /> )} {!!messages.length && ( diff --git a/src/components/pages/Advisors/index.tsx b/src/components/pages/Advisors/index.tsx index 6fef90c..f9f8d67 100644 --- a/src/components/pages/Advisors/index.tsx +++ b/src/components/pages/Advisors/index.tsx @@ -31,7 +31,7 @@ function Advisors() { useApiCall(loadData); const handleAdvisorClick = (assistant: IAssistant) => { - navigate(routes.client.advisorChat(assistant.id)); + navigate(routes.client.advisorChat(assistant.external_id)); }; return ( diff --git a/src/components/pages/BirthPlacePage/index.tsx b/src/components/pages/BirthPlacePage/index.tsx new file mode 100644 index 0000000..235670d --- /dev/null +++ b/src/components/pages/BirthPlacePage/index.tsx @@ -0,0 +1,48 @@ +import Title from "@/components/Title"; +import styles from "./styles.module.css"; +import PlacePicker from "@/components/PlacePicker"; +import { useDispatch, useSelector } from "react-redux"; +import { actions, selectors } from "@/store"; +import MainButton from "@/components/MainButton"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import routes from "@/routes"; + +function BirthPlacePage() { + const dispatch = useDispatch(); + const { t } = useTranslation(); + const navigate = useNavigate(); + const { birthPlace } = useSelector(selectors.selectQuestionnaire); + + const handleChange = (birthPlace: string) => { + return dispatch(actions.questionnaire.update({ birthPlace })); + }; + + const handleNext = () => { + navigate(routes.client.singlePaymentShortPath("chat.aura")); + }; + + return ( +
+ + Where were you born? + +

+ Please select the city where you were born. +

+ + {!!birthPlace.length && ( + + {t("next")} + + )} +
+ ); +} + +export default BirthPlacePage; diff --git a/src/components/pages/BirthPlacePage/styles.module.css b/src/components/pages/BirthPlacePage/styles.module.css new file mode 100644 index 0000000..c1b1412 --- /dev/null +++ b/src/components/pages/BirthPlacePage/styles.module.css @@ -0,0 +1,28 @@ +.page { + height: fit-content; + min-height: calc(100dvh - 50px); + background-image: url(/bunch_of_cards.webp); + display: flex; + flex-direction: column; + align-items: center; + padding-top: 40px; + gap: 16px; +} + +.title { + margin: 0; +} + +/* .button { + background: linear-gradient( + 165.54deg, + rgb(20, 19, 51) -33.39%, + rgb(32, 34, 97) 15.89%, + rgb(84, 60, 151) 55.84%, + rgb(105, 57, 162) 74.96% + ); + min-height: 0; + height: 49px; + border-radius: 12px; + margin-top: 26px; +} */ diff --git a/src/components/pages/Gender/index.tsx b/src/components/pages/Gender/index.tsx index fea93d3..bd12901 100644 --- a/src/components/pages/Gender/index.tsx +++ b/src/components/pages/Gender/index.tsx @@ -23,6 +23,9 @@ function GenderPage(): JSX.Element { if (pathName.includes("/epe/gender")) { return navigate(routes.client.epeBirthdate()); } + if (pathName.includes("/advisor-chat/gender")) { + return navigate(routes.client.advisorChatBirthdate()); + } navigate(`/questionnaire/profile/flowChoice`); }; diff --git a/src/components/pages/LoadingPage/index.tsx b/src/components/pages/LoadingPage/index.tsx new file mode 100644 index 0000000..240929b --- /dev/null +++ b/src/components/pages/LoadingPage/index.tsx @@ -0,0 +1,12 @@ +import Loader, { LoaderColor } from "@/components/Loader"; +import styles from "./styles.module.css"; + +function LoadingPage() { + return ( +
+ +
+ ); +} + +export default LoadingPage; diff --git a/src/components/pages/LoadingPage/styles.module.css b/src/components/pages/LoadingPage/styles.module.css new file mode 100644 index 0000000..b0574a3 --- /dev/null +++ b/src/components/pages/LoadingPage/styles.module.css @@ -0,0 +1,8 @@ +.page { + height: fit-content; + min-height: 100dvh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; +} diff --git a/src/components/pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage/index.tsx b/src/components/pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage/index.tsx index 0bb67d7..4f03b6e 100644 --- a/src/components/pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage/index.tsx +++ b/src/components/pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage/index.tsx @@ -8,7 +8,12 @@ import MainButton from "@/components/MainButton"; function FailPaymentPage(): JSX.Element { const { t } = useTranslation(); const navigate = useNavigate(); - const handleNext = () => navigate(routes.client.epePayment()); + const isAdvisorChat = window.location.href.includes("/advisor-chat/"); + let nextRoute = routes.client.epePayment(); + if (isAdvisorChat) { + nextRoute = routes.client.advisorChatGender(); + } + const handleNext = () => navigate(nextRoute); return (
diff --git a/src/components/pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage/index.tsx b/src/components/pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage/index.tsx index 2efb540..5168b8b 100644 --- a/src/components/pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage/index.tsx +++ b/src/components/pages/PaymentWithEmailPage/ResultPayment/SuccessPaymentPage/index.tsx @@ -1,9 +1,17 @@ import { useTranslation } from "react-i18next"; import styles from "./styles.module.css"; import Title from "@/components/Title"; +import MainButton from "@/components/MainButton"; +import { useNavigate } from "react-router-dom"; +import routes from "@/routes"; function SuccessPaymentPage(): JSX.Element { const { t } = useTranslation(); + const navigate = useNavigate(); + const isAdvisorChat = window.location.href.includes("/advisor-chat/"); + const titleText = isAdvisorChat + ? "The payment was successful" + : "The information has been sent to your email"; return (
@@ -13,9 +21,21 @@ function SuccessPaymentPage(): JSX.Element { style={{ minHeight: "98px" }} />
- The information has been sent to your email + {titleText}

{t("auweb.pay_good.text1")}

+ {isAdvisorChat && ( + + navigate( + routes.client.advisorChatPrivate("asst_WWkAlT4Ovs6gKRy6VEn9LqNS") + ) + } + > + {t("auweb.pay_good.button")} + + )}
); } diff --git a/src/components/pages/PaymentWithEmailPage/index.tsx b/src/components/pages/PaymentWithEmailPage/index.tsx index b00b116..83e5cd5 100644 --- a/src/components/pages/PaymentWithEmailPage/index.tsx +++ b/src/components/pages/PaymentWithEmailPage/index.tsx @@ -12,51 +12,63 @@ import { getClientTimezone } from "@/locales"; import ErrorText from "@/components/ErrorText"; import Title from "@/components/Title"; import NameInput from "@/components/EmailEnterPage/NameInput"; -import { - ResponseGet, - ResponsePost, - ResponsePostExistPaymentData, -} from "@/api/resources/SinglePayment"; -import { useNavigate } from "react-router-dom"; +import { useParams } from "react-router-dom"; import routes from "@/routes"; import PaymentForm from "./PaymentForm"; import { getPriceCentsToDollars } from "@/services/price"; -import { createSinglePayment } from "@/services/singlePayment"; +import { useSinglePayment } from "@/hooks/payment/useSinglePayment"; function PaymentWithEmailPage() { + const { productId } = useParams(); const { t, i18n } = useTranslation(); const tokenFromStore = useSelector(selectors.selectToken); const { signUp, user: userFromStore } = useAuth(); const api = useApi(); - const navigate = useNavigate(); const timezone = getClientTimezone(); const dispatch = useDispatch(); const birthday = useSelector(selectors.selectBirthday); - const { gender } = useSelector(selectors.selectQuestionnaire); const locale = i18n.language; const [email, setEmail] = useState(""); const [name, setName] = useState(""); const [isValidEmail, setIsValidEmail] = useState(false); - const [isValidName, setIsValidName] = useState(true); + const [isValidName, setIsValidName] = useState(productId !== "chat.aura"); const [isDisabled, setIsDisabled] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [isLoadingPage, setIsLoadingPage] = useState(false); const [isAuth, setIsAuth] = useState(false); const [apiError, setApiError] = useState(null); const [error, setError] = useState(false); - const [paymentIntent, setPaymentIntent] = useState< - ResponsePost | ResponsePostExistPaymentData | null - >(null); - const [currentProduct, setCurrentProduct] = useState(); - const returnUrl = `${window.location.protocol}//${window.location.host}/payment/result/?type=epe`; + const returnUrl = `${window.location.protocol}//${ + window.location.host + }${routes.client.paymentResult()}`; + + const [isLoadingAuth, setIsLoadingAuth] = useState(false); + + const { + product, + paymentIntent, + createSinglePayment, + isLoading: isLoadingSinglePayment, + error: errorSinglePayment, + } = useSinglePayment(); useEffect(() => { - if (isValidName && isValidEmail) { + if ( + isValidName && + isValidEmail && + !(error || apiError || errorSinglePayment?.error) + ) { setIsDisabled(false); } else { setIsDisabled(true); } - }, [isValidEmail, email, isValidName, name]); + }, [ + isValidEmail, + email, + isValidName, + name, + error, + apiError, + errorSinglePayment?.error, + ]); const handleValidEmail = (email: string) => { dispatch(actions.form.addEmail(email)); @@ -71,7 +83,7 @@ function PaymentWithEmailPage() { const authorization = async () => { try { - setIsLoading(true); + setIsLoadingAuth(true); const auth = await api.auth({ email, timezone, locale }); const { auth: { token, user }, @@ -103,6 +115,7 @@ function PaymentWithEmailPage() { dispatch(actions.status.update("registred")); setIsAuth(true); const userUpdated = await api.getUser({ token }); + setIsLoadingAuth(false); return { user: userUpdated?.user, token }; } catch (error) { console.error(error); @@ -111,19 +124,10 @@ function PaymentWithEmailPage() { } else { setError(true); } + setIsLoadingAuth(false); } }; - const getCurrentProduct = async (token: string) => { - const productsSinglePayment = await api.getSinglePaymentProducts({ - token, - }); - const currentProduct = productsSinglePayment.find( - (product) => product.key === "moons.pdf.aura" - ); - return currentProduct; - }; - const handleClick = async () => { const authData = await authorization(); if (!authData) { @@ -131,76 +135,32 @@ function PaymentWithEmailPage() { } const { user, token } = authData; - const currentProduct = await getCurrentProduct(token); - if (!currentProduct) { - setError(true); - return; - } - setCurrentProduct(currentProduct); - - const { productId, key } = currentProduct; - const paymentInfo = { - productId, - key, - }; - const paymentIntent = await createSinglePayment( + await createSinglePayment({ user, - paymentInfo, token, - email, - name, - birthday, + targetProductKey: productId || "", returnUrl, - api, - gender - ); - setPaymentIntent(paymentIntent); - setIsLoading(false); - if ("payment" in paymentIntent) { - if (paymentIntent.payment.status === "paid") - return navigate(routes.client.epeSuccessPayment()); - return navigate(routes.client.epeFailPayment()); - } + }); }; const handleAuthUser = useCallback(async () => { if (!tokenFromStore.length || !userFromStore) { return; } - setIsLoadingPage(true); - const currentProduct = await getCurrentProduct(tokenFromStore); - if (!currentProduct) { - setError(true); - return; - } - setCurrentProduct(currentProduct); - const { productId, key } = currentProduct; - const paymentInfo = { - productId, - key, - }; - const paymentIntent = await createSinglePayment( - userFromStore, - paymentInfo, - tokenFromStore, - userFromStore.email, - userFromStore.profile.full_name, - userFromStore.profile.birthday, + await createSinglePayment({ + user: userFromStore, + token: tokenFromStore, + targetProductKey: productId || "", returnUrl, - api, - gender - ); - setPaymentIntent(paymentIntent); - setIsLoadingPage(false); - setIsLoading(false); - if ("payment" in paymentIntent) { - if (paymentIntent.payment.status === "paid") - return navigate(routes.client.epeSuccessPayment()); - return navigate(routes.client.epeFailPayment()); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }); + }, [ + createSinglePayment, + productId, + returnUrl, + tokenFromStore, + userFromStore, + ]); useEffect(() => { handleAuthUser(); @@ -209,60 +169,68 @@ function PaymentWithEmailPage() { return (
- {isLoadingPage && } - {!isLoadingPage && + {(isLoadingSinglePayment || isLoadingSinglePayment) && ( + + )} + {!isLoadingSinglePayment && + !isLoadingAuth && paymentIntent && "paymentIntent" in paymentIntent && !!tokenFromStore.length && ( <> - {getPriceCentsToDollars(currentProduct?.amount || 0)}$ + {getPriceCentsToDollars(product?.amount || 0)}$ )} - {(!tokenFromStore || !paymentIntent) && !isLoadingPage && ( - <> - setIsValidName(true)} - /> - setIsValidEmail(false)} - /> + {(!tokenFromStore || + !paymentIntent || + (productId !== "chat.aura" && !name.length)) && + !isLoadingSinglePayment && + !isLoadingAuth && ( + <> + setIsValidName(productId !== "chat.aura")} + /> + setIsValidEmail(false)} + /> - - {isLoading && } - {!isLoading && - !(!apiError && !error && !isLoading && isAuth) && - t("_continue")} - {!apiError && !error && !isLoading && isAuth && ( - Success Icon - )} - - - )} - {(error || apiError) && ( + + {isLoadingSinglePayment && } + {!isLoadingSinglePayment && + !(!apiError && !error && !isLoadingSinglePayment && isAuth) && + t("_continue")} + {!apiError && !error && !isLoadingSinglePayment && isAuth && ( + Success Icon + )} + + + )} + {(error || apiError || errorSinglePayment?.error) && ( - Something went wrong + Something went wrong:{" "} + {errorSinglePayment?.error?.length && errorSinglePayment?.error} )} {apiError && ( diff --git a/src/data/products.ts b/src/data/products.ts new file mode 100644 index 0000000..7399ea0 --- /dev/null +++ b/src/data/products.ts @@ -0,0 +1,35 @@ +import routes from "@/routes"; + +interface IProductUrls { + [key: string]: string; +} + +export const productUrls: IProductUrls = { + "chat.aura": routes.client.advisorChatPrivate( + "asst_WWkAlT4Ovs6gKRy6VEn9LqNS" + ), +}; + +interface IPaymentResultPathsOfProducts { + [key: string]: IPaymentResultPathsOfProduct; +} + +interface IPaymentResultPathsOfProduct { + success: string; + fail: string; +} + +export const paymentResultPathsOfProducts: IPaymentResultPathsOfProducts = { + "moons.pdf.aura": { + success: routes.client.epeSuccessPayment(), + fail: routes.client.epeFailPayment(), + }, + "chat.aura": { + success: routes.client.advisorChatSuccessPayment(), + fail: routes.client.advisorChatFailPayment(), + }, + default: { + success: routes.client.paymentSuccess(), + fail: routes.client.paymentFail(), + }, +}; diff --git a/src/hooks/payment/useSinglePayment.ts b/src/hooks/payment/useSinglePayment.ts new file mode 100644 index 0000000..cb6d133 --- /dev/null +++ b/src/hooks/payment/useSinglePayment.ts @@ -0,0 +1,156 @@ +import { SinglePayment, useApi } from "@/api"; +import { User } from "@/api/resources/User"; +import { AuthToken } from "@/api/types"; +import { productUrls } from "@/data/products"; +import routes from "@/routes"; +import { getZodiacSignByDate } from "@/services/zodiac-sign"; +import { selectors } from "@/store"; +import { useCallback, useMemo, useState } from "react"; +import { useSelector } from "react-redux"; +import { useNavigate } from "react-router-dom"; + +interface ICreateSinglePaymentProps { + user: User; + token: AuthToken; + targetProductKey: string; + returnUrl: string; +} + +interface IErrorSinglePayment { + error?: string; +} + +export const useSinglePayment = () => { + const api = useApi(); + const navigate = useNavigate(); + const [paymentIntent, setPaymentIntent] = + useState(); + const [product, setProduct] = useState(); + const [error, setError] = useState( + {} as IErrorSinglePayment + ); + const [isLoading, setIsLoading] = useState(false); + const { gender } = useSelector(selectors.selectQuestionnaire); + const birthday = useSelector(selectors.selectBirthday); + + const getCurrentProduct = useCallback( + async (token: AuthToken, targetProductKey: string) => { + const productsSinglePayment = await api.getSinglePaymentProducts({ + token, + }); + const currentProduct = productsSinglePayment.find( + (product) => product.key === targetProductKey + ); + return currentProduct; + }, + [api] + ); + + const handlerPaymentIntentResult = useCallback( + (paymentIntent: SinglePayment.ResponsePost, type: string) => { + if (!("payment" in paymentIntent)) return; + let status = "failed"; + if (paymentIntent.payment.status === "paid") { + status = "succeeded"; + } + return navigate( + `${routes.client.paymentResult()}?redirect_status=${status}&redirect_type=${type}` + ); + }, + [navigate] + ); + + const checkProductPurchased = useCallback( + async (email: string, productKey: string, token: AuthToken) => { + try { + const purchased = await api.checkProductPurchased({ + email, + productKey, + token, + }); + + if ( + "active" in purchased && + purchased.active && + productUrls[productKey].length + ) { + return navigate(productUrls[productKey]); + } + } catch (error) { + console.error(error); + } + }, + [api, navigate] + ); + + const createSinglePayment = useCallback( + async ({ + user, + token, + targetProductKey, + returnUrl, + }: ICreateSinglePaymentProps) => { + setIsLoading(true); + const product = await getCurrentProduct(token, targetProductKey); + if (!product) { + setError({ error: "Product not found" }); + setIsLoading(false); + return; + } + setProduct(product); + await checkProductPurchased(user?.email || "", targetProductKey, token); + const paymentIntent = await api.createSinglePayment({ + token, + data: { + user: { + id: `${user?.id}`, + email: user?.email, + name: user.username || "", + sign: + user?.profile?.sign?.sign || + getZodiacSignByDate(user.profile.birthday || birthday || ""), + age: user?.profile?.age?.years || 1, + gender: user.profile.gender || gender || "", + }, + partner: { + sign: null, + age: null, + }, + paymentInfo: { + productId: product?.productId || "", + key: product?.key || "", + }, + return_url: returnUrl, + }, + }); + if ("message" in paymentIntent) { + setError({ error: paymentIntent.message }); + setIsLoading(false); + return; + } + handlerPaymentIntentResult(paymentIntent, targetProductKey); + setPaymentIntent(paymentIntent); + setIsLoading(false); + return paymentIntent; + }, + [ + api, + birthday, + checkProductPurchased, + gender, + getCurrentProduct, + handlerPaymentIntentResult, + ] + ); + + return useMemo( + () => ({ + product, + paymentIntent, + createSinglePayment, + isLoading, + error, + }), + [product, paymentIntent, createSinglePayment, isLoading, error] + ); +}; diff --git a/src/hooks/useSchemeColorByElement.tsx b/src/hooks/useSchemeColorByElement.tsx index 9beb1b7..0d70916 100644 --- a/src/hooks/useSchemeColorByElement.tsx +++ b/src/hooks/useSchemeColorByElement.tsx @@ -8,7 +8,6 @@ export const useSchemeColorByElement = ( ) => { useEffect(() => { const pageElement = element?.querySelectorAll(searchSelectors)[0]; - console.log("pageElement", pageElement); const scheme = document.querySelector('meta[name="theme-color"]'); if (scheme && !pageElement) { @@ -20,7 +19,6 @@ export const useSchemeColorByElement = ( if (colorScheme?.a === 0) { backgroundColor = "#ffffff"; } - console.log("backgroundColor", backgroundColor); if (scheme && backgroundColor.length) { return scheme.setAttribute("content", backgroundColor); } diff --git a/src/routes.ts b/src/routes.ts index 92eeb1c..be6867f 100755 --- a/src/routes.ts +++ b/src/routes.ts @@ -124,7 +124,7 @@ const routes = { // Advisors advisors: () => [host, "advisors"].join("/"), - advisorChat: (id: number) => [host, "advisors", id].join("/"), + advisorChat: (id: string) => [host, "advisors", id].join("/"), // Email - Pay - Email epeGender: () => [host, "epe", "gender"].join("/"), epeBirthdate: () => [host, "epe", "birthdate"].join("/"), @@ -132,8 +132,26 @@ const routes = { epeSuccessPayment: () => [host, "epe", "success-payment"].join("/"), epeFailPayment: () => [host, "epe", "fail-payment"].join("/"), + // Advisor short path + advisorChatGender: () => [host, "advisor-chat", "gender"].join("/"), + advisorChatBirthdate: () => [host, "advisor-chat", "birthdate"].join("/"), + advisorChatBirthtime: () => [host, "advisor-chat", "birthtime"].join("/"), + advisorChatBirthPlace: () => + [host, "advisor-chat", "birth-place"].join("/"), + advisorChatPayment: () => [host, "advisor-chat", "payment"].join("/"), + advisorChatSuccessPayment: () => + [host, "advisor-chat", "success-payment"].join("/"), + advisorChatFailPayment: () => + [host, "advisor-chat", "fail-payment"].join("/"), + advisorChatPrivate: (id?: string) => + [host, "advisor-chat-private", id].join("/"), + + singlePaymentShortPath: (productId?: string) => + [host, "single-payment", productId].join("/"), + getInformationPartner: () => [host, "get-information-partner"].join("/"), + loadingPage: () => [host, "loading-page"].join("/"), notFound: () => [host, "404"].join("/"), }, server: { @@ -194,6 +212,10 @@ const routes = { dApiTestPaymentProducts: () => [dApiHost, "payment", "test", "products"].join("/"), dApiPaymentCheckout: () => [dApiHost, "payment", "checkout"].join("/"), + dApiCheckProductPurchased: (productKey: string, email: string) => + [dApiHost, "payment", "products", `${productKey}?email=${email}`].join( + "/" + ), assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"), setExternalChatIdAssistants: (chatId: string) => @@ -230,6 +252,9 @@ export const entrypoints = [ routes.client.trialChoice(), routes.client.palmistry(), routes.client.advisors(), + routes.client.advisorChatGender(), + routes.client.advisorChatSuccessPayment(), + routes.client.advisorChatFailPayment(), ]; export const isEntrypoint = (path: string) => entrypoints.includes(path); export const isNotEntrypoint = (path: string) => !isEntrypoint(path); @@ -318,11 +343,13 @@ export const withoutFooterRoutes = [ routes.client.advisors(), routes.client.epeSuccessPayment(), routes.client.getInformationPartner(), + routes.client.advisorChatBirthPlace(), ]; export const withoutFooterPartOfRoutes = [ routes.client.questionnaire(), routes.client.advisors(), + routes.client.advisorChatPrivate(), ]; export const hasNoFooter = (path: string) => { @@ -393,6 +420,7 @@ export const withoutHeaderRoutes = [ routes.client.advisors(), routes.client.epeSuccessPayment(), routes.client.getInformationPartner(), + routes.client.advisorChatPrivate(), ]; export const hasNoHeader = (path: string) => { let result = true;