Merge branch 'preview/advisor-short-path' into 'develop'
add short path for advisor chat See merge request witapp/aura-webapp!77
This commit is contained in:
commit
10c567e169
@ -27,6 +27,7 @@ import {
|
|||||||
Assistants,
|
Assistants,
|
||||||
OpenAI,
|
OpenAI,
|
||||||
SinglePayment,
|
SinglePayment,
|
||||||
|
Products,
|
||||||
} from './resources'
|
} from './resources'
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
@ -69,7 +70,8 @@ const api = {
|
|||||||
getListRuns: createMethod<OpenAI.PayloadGetListRuns, OpenAI.ResponseGetListRuns>(OpenAI.createRequest),
|
getListRuns: createMethod<OpenAI.PayloadGetListRuns, OpenAI.ResponseGetListRuns>(OpenAI.createRequest),
|
||||||
// Single payment
|
// Single payment
|
||||||
getSinglePaymentProducts: createMethod<SinglePayment.PayloadGet, SinglePayment.ResponseGet[]>(SinglePayment.createRequestGet),
|
getSinglePaymentProducts: createMethod<SinglePayment.PayloadGet, SinglePayment.ResponseGet[]>(SinglePayment.createRequestGet),
|
||||||
createSinglePayment: createMethod<SinglePayment.PayloadPost, SinglePayment.ResponsePost | SinglePayment.ResponsePostExistPaymentData>(SinglePayment.createRequestPost),
|
createSinglePayment: createMethod<SinglePayment.PayloadPost, SinglePayment.ResponsePost>(SinglePayment.createRequestPost),
|
||||||
|
checkProductPurchased: createMethod<Products.PayloadGet, Products.ResponseGet>(Products.createRequest),
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ApiContextValue = typeof api
|
export type ApiContextValue = typeof api
|
||||||
|
|||||||
29
src/api/resources/Products.ts
Normal file
29
src/api/resources/Products.ts
Normal file
@ -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) });
|
||||||
|
};
|
||||||
@ -38,7 +38,7 @@ export interface ResponseGet {
|
|||||||
currency: string;
|
currency: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponsePost {
|
interface ResponsePostNewPaymentData {
|
||||||
paymentIntent: {
|
paymentIntent: {
|
||||||
status: string;
|
status: string;
|
||||||
data: {
|
data: {
|
||||||
@ -60,13 +60,23 @@ export interface ResponsePost {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponsePostExistPaymentData {
|
interface ResponsePostExistPaymentData {
|
||||||
payment: {
|
payment: {
|
||||||
status: string;
|
status: string;
|
||||||
invoiceId: string;
|
invoiceId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ResponsePostError {
|
||||||
|
status: string;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ResponsePost =
|
||||||
|
| ResponsePostNewPaymentData
|
||||||
|
| ResponsePostExistPaymentData
|
||||||
|
| ResponsePostError;
|
||||||
|
|
||||||
export const createRequestPost = ({ data, token }: PayloadPost): Request => {
|
export const createRequestPost = ({ data, token }: PayloadPost): Request => {
|
||||||
const url = new URL(routes.server.dApiPaymentCheckout());
|
const url = new URL(routes.server.dApiPaymentCheckout());
|
||||||
const body = JSON.stringify(data);
|
const body = JSON.stringify(data);
|
||||||
|
|||||||
@ -25,3 +25,4 @@ export * as AIRequestsV2 from "./AIRequestsV2";
|
|||||||
export * as Assistants from "./Assistants";
|
export * as Assistants from "./Assistants";
|
||||||
export * as OpenAI from "./OpenAI";
|
export * as OpenAI from "./OpenAI";
|
||||||
export * as SinglePayment from "./SinglePayment";
|
export * as SinglePayment from "./SinglePayment";
|
||||||
|
export * as Products from "./Products";
|
||||||
|
|||||||
@ -111,6 +111,8 @@ import SuccessPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/Succ
|
|||||||
import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage";
|
import FailPaymentPage from "../pages/PaymentWithEmailPage/ResultPayment/FailPaymentPage";
|
||||||
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
|
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
|
||||||
import GetInformationPartnerPage from "../pages/GetInformationPartner";
|
import GetInformationPartnerPage from "../pages/GetInformationPartner";
|
||||||
|
import BirthPlacePage from "../pages/BirthPlacePage";
|
||||||
|
import LoadingPage from "../pages/LoadingPage";
|
||||||
|
|
||||||
const isProduction = import.meta.env.MODE === "production";
|
const isProduction = import.meta.env.MODE === "production";
|
||||||
|
|
||||||
@ -252,13 +254,14 @@ function App(): JSX.Element {
|
|||||||
return (
|
return (
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
|
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
|
||||||
|
<Route path={routes.client.loadingPage()} element={<LoadingPage />} />
|
||||||
{/* Email - Pay - Email */}
|
{/* Email - Pay - Email */}
|
||||||
<Route path={routes.client.epeGender()} element={<GenderPage />} />
|
<Route path={routes.client.epeGender()} element={<GenderPage />} />
|
||||||
<Route path={routes.client.epeBirthdate()} element={<BirthdayPage />} />
|
<Route path={routes.client.epeBirthdate()} element={<BirthdayPage />} />
|
||||||
<Route
|
{/* <Route
|
||||||
path={routes.client.epePayment()}
|
path={routes.client.epePayment()}
|
||||||
element={<PaymentWithEmailPage />}
|
element={<PaymentWithEmailPage />}
|
||||||
/>
|
/> */}
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.epeSuccessPayment()}
|
path={routes.client.epeSuccessPayment()}
|
||||||
element={<SuccessPaymentPage />}
|
element={<SuccessPaymentPage />}
|
||||||
@ -269,6 +272,68 @@ function App(): JSX.Element {
|
|||||||
/>
|
/>
|
||||||
{/* Email - Pay - Email */}
|
{/* Email - Pay - Email */}
|
||||||
|
|
||||||
|
{/* Advisor short path */}
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<CheckPurchasedSingleProductOutlet
|
||||||
|
productKey="chat.aura"
|
||||||
|
isProductPage={false}
|
||||||
|
failedUrl={routes.client.advisorChatPrivate(
|
||||||
|
"asst_WWkAlT4Ovs6gKRy6VEn9LqNS"
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatGender()}
|
||||||
|
element={<GenderPage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatBirthdate()}
|
||||||
|
element={<BirthdayPage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatBirthtime()}
|
||||||
|
element={<BirthtimePage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatBirthPlace()}
|
||||||
|
element={<BirthPlacePage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatSuccessPayment()}
|
||||||
|
element={<SuccessPaymentPage />}
|
||||||
|
/>
|
||||||
|
<Route
|
||||||
|
path={routes.client.advisorChatFailPayment()}
|
||||||
|
element={<FailPaymentPage />}
|
||||||
|
/>
|
||||||
|
</Route>
|
||||||
|
|
||||||
|
<Route
|
||||||
|
element={
|
||||||
|
<CheckPurchasedSingleProductOutlet
|
||||||
|
isProductPage={true}
|
||||||
|
failedUrl={routes.client.advisorChatGender()}
|
||||||
|
productKey="chat.aura"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<Route path={`${routes.client.advisorChatPrivate()}`}>
|
||||||
|
<Route path=":id" element={<AdvisorChatPage />} />
|
||||||
|
</Route>
|
||||||
|
</Route>
|
||||||
|
{/* Advisor short path */}
|
||||||
|
|
||||||
|
{/* Single Payment Page Short Path */}
|
||||||
|
<Route
|
||||||
|
path={routes.client.singlePaymentShortPath()}
|
||||||
|
element={<PaymentWithEmailPage />}
|
||||||
|
>
|
||||||
|
<Route path=":productId" element={<PaymentWithEmailPage />} />
|
||||||
|
</Route>
|
||||||
|
{/* Single Payment Page Short Path */}
|
||||||
|
|
||||||
{/* Test Routes Start */}
|
{/* Test Routes Start */}
|
||||||
<Route path={routes.client.notFound()} element={<NotFoundPage />} />
|
<Route path={routes.client.notFound()} element={<NotFoundPage />} />
|
||||||
<Route path={routes.client.gender()} element={<GenderPage />}>
|
<Route path={routes.client.gender()} element={<GenderPage />}>
|
||||||
@ -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 <LoadingPage />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
isProductPage &&
|
||||||
|
(!("active" in data) || !data.active || !token.length || !user?.email)
|
||||||
|
) {
|
||||||
|
return <Navigate to={failedUrl} replace={true} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isProductPage && data && "active" in data && data.active) {
|
||||||
|
return <Navigate to={failedUrl} replace={true} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <Outlet />;
|
||||||
|
}
|
||||||
|
|
||||||
function AuthorizedUserOutlet(): JSX.Element {
|
function AuthorizedUserOutlet(): JSX.Element {
|
||||||
const status = useSelector(selectors.selectStatus);
|
const status = useSelector(selectors.selectStatus);
|
||||||
const { user } = useAuth();
|
const { user } = useAuth();
|
||||||
|
|||||||
@ -17,9 +17,13 @@ function BirthdayPage(): JSX.Element {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const birthdate = useSelector(selectors.selectBirthdate);
|
const birthdate = useSelector(selectors.selectBirthdate);
|
||||||
const [isDisabled, setIsDisabled] = useState(true);
|
const [isDisabled, setIsDisabled] = useState(true);
|
||||||
const nextRoute = window.location.href.includes("/epe/")
|
let nextRoute = routes.client.didYouKnow();
|
||||||
? routes.client.epePayment()
|
if (window.location.href.includes("/epe/")) {
|
||||||
: routes.client.didYouKnow();
|
nextRoute = routes.client.singlePaymentShortPath("moons.pdf.aura");
|
||||||
|
}
|
||||||
|
if (window.location.href.includes("/advisor-chat/")) {
|
||||||
|
nextRoute = routes.client.advisorChatBirthtime();
|
||||||
|
}
|
||||||
const handleNext = () => navigate(nextRoute);
|
const handleNext = () => navigate(nextRoute);
|
||||||
const handleValid = (birthdate: string) => {
|
const handleValid = (birthdate: string) => {
|
||||||
dispatch(actions.form.addDate(birthdate));
|
dispatch(actions.form.addDate(birthdate));
|
||||||
|
|||||||
@ -1,28 +1,34 @@
|
|||||||
import { useNavigate } from "react-router-dom"
|
import { useNavigate } from "react-router-dom";
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from "react-i18next";
|
||||||
import { useDispatch, useSelector } from 'react-redux'
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { actions, selectors } from '@/store'
|
import { actions, selectors } from "@/store";
|
||||||
import { TimePicker } from "../DateTimePicker"
|
import { TimePicker } from "../DateTimePicker";
|
||||||
import Title from "../Title"
|
import Title from "../Title";
|
||||||
import MainButton from "../MainButton"
|
import MainButton from "../MainButton";
|
||||||
import routes from "@/routes"
|
import routes from "@/routes";
|
||||||
import './styles.css'
|
import "./styles.css";
|
||||||
|
|
||||||
function BirthtimePage(): JSX.Element {
|
function BirthtimePage(): JSX.Element {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation();
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const birthtime = useSelector(selectors.selectBirthtime)
|
const birthtime = useSelector(selectors.selectBirthtime);
|
||||||
const handleNext = () => navigate(routes.client.createProfile())
|
let nextRoute = routes.client.createProfile();
|
||||||
const handleChange = (value: string) => dispatch(actions.form.addTime(value))
|
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 (
|
return (
|
||||||
<section className='page'>
|
<section className="page">
|
||||||
<Title variant="h2" className="mt-24">{t('born_time_question')}</Title>
|
<Title variant="h2" className="mt-24">
|
||||||
<p className="description">{t('nasa_data_using')}</p>
|
{t("born_time_question")}
|
||||||
<TimePicker value={birthtime} onChange={handleChange}/>
|
</Title>
|
||||||
<MainButton onClick={handleNext}>{t('next')}</MainButton>
|
<p className="description">{t("nasa_data_using")}</p>
|
||||||
|
<TimePicker value={birthtime} onChange={handleChange} />
|
||||||
|
<MainButton onClick={handleNext}>{t("next")}</MainButton>
|
||||||
</section>
|
</section>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default BirthtimePage
|
export default BirthtimePage;
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
import { useNavigate } from "react-router-dom";
|
||||||
import routes from "@/routes";
|
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useSearchParams } from "react-router-dom";
|
import { useSearchParams } from "react-router-dom";
|
||||||
import { useDispatch } from "react-redux";
|
import { useDispatch } from "react-redux";
|
||||||
@ -8,6 +7,7 @@ import { actions } from "@/store";
|
|||||||
// import { useAuth } from "@/auth";
|
// import { useAuth } from "@/auth";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import Loader from "@/components/Loader";
|
import Loader from "@/components/Loader";
|
||||||
|
import { paymentResultPathsOfProducts } from "@/data/products";
|
||||||
|
|
||||||
function PaymentResultPage(): JSX.Element {
|
function PaymentResultPage(): JSX.Element {
|
||||||
// const api = useApi();
|
// const api = useApi();
|
||||||
@ -16,7 +16,7 @@ function PaymentResultPage(): JSX.Element {
|
|||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const status = searchParams.get("redirect_status");
|
const status = searchParams.get("redirect_status");
|
||||||
const type = searchParams.get("type");
|
const redirect_type = searchParams.get("redirect_type");
|
||||||
// const { id } = useParams();
|
// const { id } = useParams();
|
||||||
// const requestTimeOutRef = useRef<NodeJS.Timeout>();
|
// const requestTimeOutRef = useRef<NodeJS.Timeout>();
|
||||||
const [isLoading] = useState(true);
|
const [isLoading] = useState(true);
|
||||||
@ -90,18 +90,19 @@ function PaymentResultPage(): JSX.Element {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === "succeeded") {
|
if (status === "succeeded") {
|
||||||
dispatch(actions.status.update("subscribed"));
|
dispatch(actions.status.update("subscribed"));
|
||||||
let successPaymentRoute = routes.client.paymentSuccess();
|
if (
|
||||||
if (type === "epe") {
|
!paymentResultPathsOfProducts[redirect_type || ""] ||
|
||||||
successPaymentRoute = routes.client.epeSuccessPayment();
|
!redirect_type
|
||||||
|
) {
|
||||||
|
return navigate(paymentResultPathsOfProducts.default.success);
|
||||||
}
|
}
|
||||||
return navigate(successPaymentRoute);
|
return navigate(paymentResultPathsOfProducts[redirect_type].success);
|
||||||
}
|
}
|
||||||
let failPaymentRoute = routes.client.paymentFail();
|
if (!paymentResultPathsOfProducts[redirect_type || ""] || !redirect_type) {
|
||||||
if (type === "epe") {
|
return navigate(paymentResultPathsOfProducts.default.fail);
|
||||||
failPaymentRoute = routes.client.epeFailPayment();
|
|
||||||
}
|
}
|
||||||
return navigate(failPaymentRoute);
|
return navigate(paymentResultPathsOfProducts[redirect_type].fail);
|
||||||
}, [navigate, status, dispatch]);
|
}, [navigate, status, dispatch, redirect_type]);
|
||||||
|
|
||||||
return <div className={styles.page}>{isLoading && <Loader />}</div>;
|
return <div className={styles.page}>{isLoading && <Loader />}</div>;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,6 @@ import { selectors } from "@/store";
|
|||||||
import { useCallback, useState } from "react";
|
import { useCallback, useState } from "react";
|
||||||
import {
|
import {
|
||||||
ResponsePost,
|
ResponsePost,
|
||||||
ResponsePostExistPaymentData,
|
|
||||||
} from "@/api/resources/SinglePayment";
|
} from "@/api/resources/SinglePayment";
|
||||||
import { createSinglePayment } from "@/services/singlePayment";
|
import { createSinglePayment } from "@/services/singlePayment";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
@ -27,7 +26,7 @@ function AddConsultationPage() {
|
|||||||
const tokenFromStore = useSelector(selectors.selectToken);
|
const tokenFromStore = useSelector(selectors.selectToken);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [paymentIntent, setPaymentIntent] = useState<
|
const [paymentIntent, setPaymentIntent] = useState<
|
||||||
ResponsePost | ResponsePostExistPaymentData | null
|
ResponsePost | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
const returnUrl = `${window.location.protocol}//${
|
const returnUrl = `${window.location.protocol}//${
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import PaymentAddress from "../../components/PaymentAddress";
|
|||||||
import { createSinglePayment } from "@/services/singlePayment";
|
import { createSinglePayment } from "@/services/singlePayment";
|
||||||
import {
|
import {
|
||||||
ResponsePost,
|
ResponsePost,
|
||||||
ResponsePostExistPaymentData,
|
|
||||||
} from "@/api/resources/SinglePayment";
|
} from "@/api/resources/SinglePayment";
|
||||||
import { useAuth } from "@/auth";
|
import { useAuth } from "@/auth";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
@ -28,7 +27,7 @@ function AddReportPage() {
|
|||||||
const api = useApi();
|
const api = useApi();
|
||||||
const tokenFromStore = useSelector(selectors.selectToken);
|
const tokenFromStore = useSelector(selectors.selectToken);
|
||||||
const [paymentIntent, setPaymentIntent] = useState<
|
const [paymentIntent, setPaymentIntent] = useState<
|
||||||
ResponsePost | ResponsePostExistPaymentData | null
|
ResponsePost | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import { createSinglePayment } from "@/services/singlePayment";
|
|||||||
import Loader, { LoaderColor } from "@/components/Loader";
|
import Loader, { LoaderColor } from "@/components/Loader";
|
||||||
import {
|
import {
|
||||||
ResponsePost,
|
ResponsePost,
|
||||||
ResponsePostExistPaymentData,
|
|
||||||
} from "@/api/resources/SinglePayment";
|
} from "@/api/resources/SinglePayment";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import { getPriceCentsToDollars } from "@/services/price";
|
import { getPriceCentsToDollars } from "@/services/price";
|
||||||
@ -42,7 +41,7 @@ function UnlimitedReadingsPage() {
|
|||||||
const tokenFromStore = useSelector(selectors.selectToken);
|
const tokenFromStore = useSelector(selectors.selectToken);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [paymentIntent, setPaymentIntent] = useState<
|
const [paymentIntent, setPaymentIntent] = useState<
|
||||||
ResponsePost | ResponsePostExistPaymentData | null
|
ResponsePost | null
|
||||||
>(null);
|
>(null);
|
||||||
const [isError, setIsError] = useState(false);
|
const [isError, setIsError] = useState(false);
|
||||||
const returnUrl = `${window.location.protocol}//${
|
const returnUrl = `${window.location.protocol}//${
|
||||||
|
|||||||
@ -4,6 +4,7 @@ interface IChatHeaderProps {
|
|||||||
name: string;
|
name: string;
|
||||||
avatar: string;
|
avatar: string;
|
||||||
classNameContainer?: string;
|
classNameContainer?: string;
|
||||||
|
hasBackButton?: boolean;
|
||||||
clickBackButton: () => void;
|
clickBackButton: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,13 +12,16 @@ function ChatHeader({
|
|||||||
name,
|
name,
|
||||||
avatar,
|
avatar,
|
||||||
classNameContainer = "",
|
classNameContainer = "",
|
||||||
|
hasBackButton = true,
|
||||||
clickBackButton,
|
clickBackButton,
|
||||||
}: IChatHeaderProps) {
|
}: IChatHeaderProps) {
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.container} ${classNameContainer}`}>
|
<div className={`${styles.container} ${classNameContainer}`}>
|
||||||
<div className={styles["back-button"]} onClick={clickBackButton}>
|
{hasBackButton && (
|
||||||
<div className={styles["arrow"]} /> Advisors
|
<div className={styles["back-button"]} onClick={clickBackButton}>
|
||||||
</div>
|
<div className={styles["arrow"]} /> Advisors
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className={styles.name}>
|
<div className={styles.name}>
|
||||||
{name}
|
{name}
|
||||||
<span className={styles["online-status"]} />
|
<span className={styles["online-status"]} />
|
||||||
|
|||||||
@ -16,6 +16,7 @@ import useDetectScroll from "@smakss/react-scroll-direction";
|
|||||||
import { getZodiacSignByDate } from "@/services/zodiac-sign";
|
import { getZodiacSignByDate } from "@/services/zodiac-sign";
|
||||||
|
|
||||||
function AdvisorChatPage() {
|
function AdvisorChatPage() {
|
||||||
|
const isPrivateChat = window.location.href.includes("/advisor-chat-private/");
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
@ -25,7 +26,9 @@ function AdvisorChatPage() {
|
|||||||
const birthdate = useSelector(selectors.selectBirthdate);
|
const birthdate = useSelector(selectors.selectBirthdate);
|
||||||
const zodiacSign = getZodiacSignByDate(birthdate);
|
const zodiacSign = getZodiacSignByDate(birthdate);
|
||||||
const { username } = useSelector(selectors.selectUser);
|
const { username } = useSelector(selectors.selectUser);
|
||||||
const { gender } = useSelector(selectors.selectQuestionnaire);
|
const { gender, birthtime, birthPlace } = useSelector(
|
||||||
|
selectors.selectQuestionnaire
|
||||||
|
);
|
||||||
const [assistant, setAssistant] = useState<IAssistant>();
|
const [assistant, setAssistant] = useState<IAssistant>();
|
||||||
const [messageText, setMessageText] = useState("");
|
const [messageText, setMessageText] = useState("");
|
||||||
const [textareaRows, setTextareaRows] = useState(1);
|
const [textareaRows, setTextareaRows] = useState(1);
|
||||||
@ -88,7 +91,7 @@ function AdvisorChatPage() {
|
|||||||
idAssistant: string | number
|
idAssistant: string | number
|
||||||
) => {
|
) => {
|
||||||
const currentAssistant = aiAssistants.find(
|
const currentAssistant = aiAssistants.find(
|
||||||
(a) => a.id === Number(idAssistant)
|
(a) => a.external_id === idAssistant
|
||||||
);
|
);
|
||||||
return currentAssistant;
|
return currentAssistant;
|
||||||
};
|
};
|
||||||
@ -196,10 +199,17 @@ function AdvisorChatPage() {
|
|||||||
return run;
|
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 - ${
|
const content = `#USER INFO: zodiac sign - ${zodiacSign}; gender - ${gender}; birthdate - ${birthdate}; name - ${
|
||||||
username || "unknown"
|
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({
|
const message = await api.createMessage({
|
||||||
token: openAiToken,
|
token: openAiToken,
|
||||||
method: "POST",
|
method: "POST",
|
||||||
@ -260,7 +270,8 @@ function AdvisorChatPage() {
|
|||||||
threadId = assistant.external_chat_id;
|
threadId = assistant.external_chat_id;
|
||||||
assistantId = assistant.external_id || "";
|
assistantId = assistant.external_id || "";
|
||||||
} else {
|
} else {
|
||||||
const thread = await createThread(messageText);
|
const content = getContentMessage(messageText);
|
||||||
|
const thread = await createThread(content);
|
||||||
threadId = thread.id;
|
threadId = thread.id;
|
||||||
assistantId = assistant?.external_id || "";
|
assistantId = assistant?.external_id || "";
|
||||||
|
|
||||||
@ -321,6 +332,7 @@ function AdvisorChatPage() {
|
|||||||
avatar={assistant?.photo?.th2x || ""}
|
avatar={assistant?.photo?.th2x || ""}
|
||||||
classNameContainer={styles["header-container"]}
|
classNameContainer={styles["header-container"]}
|
||||||
clickBackButton={() => navigate(-1)}
|
clickBackButton={() => navigate(-1)}
|
||||||
|
hasBackButton={!isPrivateChat}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!!messages.length && (
|
{!!messages.length && (
|
||||||
|
|||||||
@ -31,7 +31,7 @@ function Advisors() {
|
|||||||
useApiCall<Assistants.Response>(loadData);
|
useApiCall<Assistants.Response>(loadData);
|
||||||
|
|
||||||
const handleAdvisorClick = (assistant: IAssistant) => {
|
const handleAdvisorClick = (assistant: IAssistant) => {
|
||||||
navigate(routes.client.advisorChat(assistant.id));
|
navigate(routes.client.advisorChat(assistant.external_id));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
48
src/components/pages/BirthPlacePage/index.tsx
Normal file
48
src/components/pages/BirthPlacePage/index.tsx
Normal file
@ -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 (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<Title variant="h1" className={styles.title}>
|
||||||
|
Where were you born?
|
||||||
|
</Title>
|
||||||
|
<p className={styles.description}>
|
||||||
|
Please select the city where you were born.
|
||||||
|
</p>
|
||||||
|
<PlacePicker
|
||||||
|
value={birthPlace}
|
||||||
|
name="birthPlace"
|
||||||
|
maxLength={1000}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
{!!birthPlace.length && (
|
||||||
|
<MainButton className={styles.button} onClick={handleNext}>
|
||||||
|
{t("next")}
|
||||||
|
</MainButton>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default BirthPlacePage;
|
||||||
28
src/components/pages/BirthPlacePage/styles.module.css
Normal file
28
src/components/pages/BirthPlacePage/styles.module.css
Normal file
@ -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;
|
||||||
|
} */
|
||||||
@ -23,6 +23,9 @@ function GenderPage(): JSX.Element {
|
|||||||
if (pathName.includes("/epe/gender")) {
|
if (pathName.includes("/epe/gender")) {
|
||||||
return navigate(routes.client.epeBirthdate());
|
return navigate(routes.client.epeBirthdate());
|
||||||
}
|
}
|
||||||
|
if (pathName.includes("/advisor-chat/gender")) {
|
||||||
|
return navigate(routes.client.advisorChatBirthdate());
|
||||||
|
}
|
||||||
navigate(`/questionnaire/profile/flowChoice`);
|
navigate(`/questionnaire/profile/flowChoice`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
12
src/components/pages/LoadingPage/index.tsx
Normal file
12
src/components/pages/LoadingPage/index.tsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Loader, { LoaderColor } from "@/components/Loader";
|
||||||
|
import styles from "./styles.module.css";
|
||||||
|
|
||||||
|
function LoadingPage() {
|
||||||
|
return (
|
||||||
|
<section className={`${styles.page} page`}>
|
||||||
|
<Loader color={LoaderColor.Black} />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default LoadingPage;
|
||||||
8
src/components/pages/LoadingPage/styles.module.css
Normal file
8
src/components/pages/LoadingPage/styles.module.css
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
.page {
|
||||||
|
height: fit-content;
|
||||||
|
min-height: 100dvh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
@ -8,7 +8,12 @@ import MainButton from "@/components/MainButton";
|
|||||||
function FailPaymentPage(): JSX.Element {
|
function FailPaymentPage(): JSX.Element {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
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 (
|
return (
|
||||||
<section className={`${styles.page} page`}>
|
<section className={`${styles.page} page`}>
|
||||||
|
|||||||
@ -1,9 +1,17 @@
|
|||||||
import { useTranslation } from "react-i18next";
|
import { useTranslation } from "react-i18next";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import Title from "@/components/Title";
|
import Title from "@/components/Title";
|
||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import routes from "@/routes";
|
||||||
|
|
||||||
function SuccessPaymentPage(): JSX.Element {
|
function SuccessPaymentPage(): JSX.Element {
|
||||||
const { t } = useTranslation();
|
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 (
|
return (
|
||||||
<section className={`${styles.page} page`}>
|
<section className={`${styles.page} page`}>
|
||||||
@ -13,9 +21,21 @@ function SuccessPaymentPage(): JSX.Element {
|
|||||||
style={{ minHeight: "98px" }}
|
style={{ minHeight: "98px" }}
|
||||||
/>
|
/>
|
||||||
<div className={styles.text}>
|
<div className={styles.text}>
|
||||||
<Title variant="h1">The information has been sent to your email</Title>
|
<Title variant="h1">{titleText}</Title>
|
||||||
<p>{t("auweb.pay_good.text1")}</p>
|
<p>{t("auweb.pay_good.text1")}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{isAdvisorChat && (
|
||||||
|
<MainButton
|
||||||
|
className={styles.button}
|
||||||
|
onClick={() =>
|
||||||
|
navigate(
|
||||||
|
routes.client.advisorChatPrivate("asst_WWkAlT4Ovs6gKRy6VEn9LqNS")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("auweb.pay_good.button")}
|
||||||
|
</MainButton>
|
||||||
|
)}
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,51 +12,63 @@ import { getClientTimezone } from "@/locales";
|
|||||||
import ErrorText from "@/components/ErrorText";
|
import ErrorText from "@/components/ErrorText";
|
||||||
import Title from "@/components/Title";
|
import Title from "@/components/Title";
|
||||||
import NameInput from "@/components/EmailEnterPage/NameInput";
|
import NameInput from "@/components/EmailEnterPage/NameInput";
|
||||||
import {
|
import { useParams } from "react-router-dom";
|
||||||
ResponseGet,
|
|
||||||
ResponsePost,
|
|
||||||
ResponsePostExistPaymentData,
|
|
||||||
} from "@/api/resources/SinglePayment";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import routes from "@/routes";
|
import routes from "@/routes";
|
||||||
import PaymentForm from "./PaymentForm";
|
import PaymentForm from "./PaymentForm";
|
||||||
import { getPriceCentsToDollars } from "@/services/price";
|
import { getPriceCentsToDollars } from "@/services/price";
|
||||||
import { createSinglePayment } from "@/services/singlePayment";
|
import { useSinglePayment } from "@/hooks/payment/useSinglePayment";
|
||||||
|
|
||||||
function PaymentWithEmailPage() {
|
function PaymentWithEmailPage() {
|
||||||
|
const { productId } = useParams();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const tokenFromStore = useSelector(selectors.selectToken);
|
const tokenFromStore = useSelector(selectors.selectToken);
|
||||||
const { signUp, user: userFromStore } = useAuth();
|
const { signUp, user: userFromStore } = useAuth();
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const navigate = useNavigate();
|
|
||||||
const timezone = getClientTimezone();
|
const timezone = getClientTimezone();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const birthday = useSelector(selectors.selectBirthday);
|
const birthday = useSelector(selectors.selectBirthday);
|
||||||
const { gender } = useSelector(selectors.selectQuestionnaire);
|
|
||||||
const locale = i18n.language;
|
const locale = i18n.language;
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [name, setName] = useState("");
|
const [name, setName] = useState("");
|
||||||
const [isValidEmail, setIsValidEmail] = useState(false);
|
const [isValidEmail, setIsValidEmail] = useState(false);
|
||||||
const [isValidName, setIsValidName] = useState(true);
|
const [isValidName, setIsValidName] = useState(productId !== "chat.aura");
|
||||||
const [isDisabled, setIsDisabled] = useState(true);
|
const [isDisabled, setIsDisabled] = useState(true);
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
|
||||||
const [isLoadingPage, setIsLoadingPage] = useState(false);
|
|
||||||
const [isAuth, setIsAuth] = useState(false);
|
const [isAuth, setIsAuth] = useState(false);
|
||||||
const [apiError, setApiError] = useState<ApiError | null>(null);
|
const [apiError, setApiError] = useState<ApiError | null>(null);
|
||||||
const [error, setError] = useState<boolean>(false);
|
const [error, setError] = useState<boolean>(false);
|
||||||
const [paymentIntent, setPaymentIntent] = useState<
|
const returnUrl = `${window.location.protocol}//${
|
||||||
ResponsePost | ResponsePostExistPaymentData | null
|
window.location.host
|
||||||
>(null);
|
}${routes.client.paymentResult()}`;
|
||||||
const [currentProduct, setCurrentProduct] = useState<ResponseGet>();
|
|
||||||
const returnUrl = `${window.location.protocol}//${window.location.host}/payment/result/?type=epe`;
|
const [isLoadingAuth, setIsLoadingAuth] = useState<boolean>(false);
|
||||||
|
|
||||||
|
const {
|
||||||
|
product,
|
||||||
|
paymentIntent,
|
||||||
|
createSinglePayment,
|
||||||
|
isLoading: isLoadingSinglePayment,
|
||||||
|
error: errorSinglePayment,
|
||||||
|
} = useSinglePayment();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (isValidName && isValidEmail) {
|
if (
|
||||||
|
isValidName &&
|
||||||
|
isValidEmail &&
|
||||||
|
!(error || apiError || errorSinglePayment?.error)
|
||||||
|
) {
|
||||||
setIsDisabled(false);
|
setIsDisabled(false);
|
||||||
} else {
|
} else {
|
||||||
setIsDisabled(true);
|
setIsDisabled(true);
|
||||||
}
|
}
|
||||||
}, [isValidEmail, email, isValidName, name]);
|
}, [
|
||||||
|
isValidEmail,
|
||||||
|
email,
|
||||||
|
isValidName,
|
||||||
|
name,
|
||||||
|
error,
|
||||||
|
apiError,
|
||||||
|
errorSinglePayment?.error,
|
||||||
|
]);
|
||||||
|
|
||||||
const handleValidEmail = (email: string) => {
|
const handleValidEmail = (email: string) => {
|
||||||
dispatch(actions.form.addEmail(email));
|
dispatch(actions.form.addEmail(email));
|
||||||
@ -71,7 +83,7 @@ function PaymentWithEmailPage() {
|
|||||||
|
|
||||||
const authorization = async () => {
|
const authorization = async () => {
|
||||||
try {
|
try {
|
||||||
setIsLoading(true);
|
setIsLoadingAuth(true);
|
||||||
const auth = await api.auth({ email, timezone, locale });
|
const auth = await api.auth({ email, timezone, locale });
|
||||||
const {
|
const {
|
||||||
auth: { token, user },
|
auth: { token, user },
|
||||||
@ -103,6 +115,7 @@ function PaymentWithEmailPage() {
|
|||||||
dispatch(actions.status.update("registred"));
|
dispatch(actions.status.update("registred"));
|
||||||
setIsAuth(true);
|
setIsAuth(true);
|
||||||
const userUpdated = await api.getUser({ token });
|
const userUpdated = await api.getUser({ token });
|
||||||
|
setIsLoadingAuth(false);
|
||||||
return { user: userUpdated?.user, token };
|
return { user: userUpdated?.user, token };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(error);
|
console.error(error);
|
||||||
@ -111,19 +124,10 @@ function PaymentWithEmailPage() {
|
|||||||
} else {
|
} else {
|
||||||
setError(true);
|
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 handleClick = async () => {
|
||||||
const authData = await authorization();
|
const authData = await authorization();
|
||||||
if (!authData) {
|
if (!authData) {
|
||||||
@ -131,76 +135,32 @@ function PaymentWithEmailPage() {
|
|||||||
}
|
}
|
||||||
const { user, token } = authData;
|
const { user, token } = authData;
|
||||||
|
|
||||||
const currentProduct = await getCurrentProduct(token);
|
await createSinglePayment({
|
||||||
if (!currentProduct) {
|
|
||||||
setError(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCurrentProduct(currentProduct);
|
|
||||||
|
|
||||||
const { productId, key } = currentProduct;
|
|
||||||
const paymentInfo = {
|
|
||||||
productId,
|
|
||||||
key,
|
|
||||||
};
|
|
||||||
const paymentIntent = await createSinglePayment(
|
|
||||||
user,
|
user,
|
||||||
paymentInfo,
|
|
||||||
token,
|
token,
|
||||||
email,
|
targetProductKey: productId || "",
|
||||||
name,
|
|
||||||
birthday,
|
|
||||||
returnUrl,
|
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 () => {
|
const handleAuthUser = useCallback(async () => {
|
||||||
if (!tokenFromStore.length || !userFromStore) {
|
if (!tokenFromStore.length || !userFromStore) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setIsLoadingPage(true);
|
|
||||||
const currentProduct = await getCurrentProduct(tokenFromStore);
|
|
||||||
if (!currentProduct) {
|
|
||||||
setError(true);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
setCurrentProduct(currentProduct);
|
|
||||||
|
|
||||||
const { productId, key } = currentProduct;
|
await createSinglePayment({
|
||||||
const paymentInfo = {
|
user: userFromStore,
|
||||||
productId,
|
token: tokenFromStore,
|
||||||
key,
|
targetProductKey: productId || "",
|
||||||
};
|
|
||||||
const paymentIntent = await createSinglePayment(
|
|
||||||
userFromStore,
|
|
||||||
paymentInfo,
|
|
||||||
tokenFromStore,
|
|
||||||
userFromStore.email,
|
|
||||||
userFromStore.profile.full_name,
|
|
||||||
userFromStore.profile.birthday,
|
|
||||||
returnUrl,
|
returnUrl,
|
||||||
api,
|
});
|
||||||
gender
|
}, [
|
||||||
);
|
createSinglePayment,
|
||||||
setPaymentIntent(paymentIntent);
|
productId,
|
||||||
setIsLoadingPage(false);
|
returnUrl,
|
||||||
setIsLoading(false);
|
tokenFromStore,
|
||||||
if ("payment" in paymentIntent) {
|
userFromStore,
|
||||||
if (paymentIntent.payment.status === "paid")
|
]);
|
||||||
return navigate(routes.client.epeSuccessPayment());
|
|
||||||
return navigate(routes.client.epeFailPayment());
|
|
||||||
}
|
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
handleAuthUser();
|
handleAuthUser();
|
||||||
@ -209,60 +169,68 @@ function PaymentWithEmailPage() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.page} page`}>
|
<div className={`${styles.page} page`}>
|
||||||
{isLoadingPage && <Loader color={LoaderColor.Black} />}
|
{(isLoadingSinglePayment || isLoadingSinglePayment) && (
|
||||||
{!isLoadingPage &&
|
<Loader color={LoaderColor.Black} />
|
||||||
|
)}
|
||||||
|
{!isLoadingSinglePayment &&
|
||||||
|
!isLoadingAuth &&
|
||||||
paymentIntent &&
|
paymentIntent &&
|
||||||
"paymentIntent" in paymentIntent &&
|
"paymentIntent" in paymentIntent &&
|
||||||
!!tokenFromStore.length && (
|
!!tokenFromStore.length && (
|
||||||
<>
|
<>
|
||||||
<Title variant="h1" className={styles.title}>
|
<Title variant="h1" className={styles.title}>
|
||||||
{getPriceCentsToDollars(currentProduct?.amount || 0)}$
|
{getPriceCentsToDollars(product?.amount || 0)}$
|
||||||
</Title>
|
</Title>
|
||||||
<PaymentForm
|
<PaymentForm
|
||||||
stripePublicKey={paymentIntent.paymentIntent.data.public_key}
|
stripePublicKey={paymentIntent.paymentIntent.data.public_key}
|
||||||
clientSecret={paymentIntent.paymentIntent.data.client_secret}
|
clientSecret={paymentIntent.paymentIntent.data.client_secret}
|
||||||
returnUrl={returnUrl}
|
returnUrl={`${returnUrl}?redirect_type=${product?.key}`}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(!tokenFromStore || !paymentIntent) && !isLoadingPage && (
|
{(!tokenFromStore ||
|
||||||
<>
|
!paymentIntent ||
|
||||||
<NameInput
|
(productId !== "chat.aura" && !name.length)) &&
|
||||||
value={name}
|
!isLoadingSinglePayment &&
|
||||||
placeholder="Your name"
|
!isLoadingAuth && (
|
||||||
onValid={handleValidName}
|
<>
|
||||||
onInvalid={() => setIsValidName(true)}
|
<NameInput
|
||||||
/>
|
value={name}
|
||||||
<EmailInput
|
placeholder="Your name"
|
||||||
name="email"
|
onValid={handleValidName}
|
||||||
value={email}
|
onInvalid={() => setIsValidName(productId !== "chat.aura")}
|
||||||
placeholder={t("your_email")}
|
/>
|
||||||
onValid={handleValidEmail}
|
<EmailInput
|
||||||
onInvalid={() => setIsValidEmail(false)}
|
name="email"
|
||||||
/>
|
value={email}
|
||||||
|
placeholder={t("your_email")}
|
||||||
|
onValid={handleValidEmail}
|
||||||
|
onInvalid={() => setIsValidEmail(false)}
|
||||||
|
/>
|
||||||
|
|
||||||
<MainButton
|
<MainButton
|
||||||
className={styles.button}
|
className={styles.button}
|
||||||
onClick={handleClick}
|
onClick={handleClick}
|
||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
>
|
>
|
||||||
{isLoading && <Loader color={LoaderColor.White} />}
|
{isLoadingSinglePayment && <Loader color={LoaderColor.White} />}
|
||||||
{!isLoading &&
|
{!isLoadingSinglePayment &&
|
||||||
!(!apiError && !error && !isLoading && isAuth) &&
|
!(!apiError && !error && !isLoadingSinglePayment && isAuth) &&
|
||||||
t("_continue")}
|
t("_continue")}
|
||||||
{!apiError && !error && !isLoading && isAuth && (
|
{!apiError && !error && !isLoadingSinglePayment && isAuth && (
|
||||||
<img
|
<img
|
||||||
className={styles["success-icon"]}
|
className={styles["success-icon"]}
|
||||||
src="/SuccessIcon.png"
|
src="/SuccessIcon.png"
|
||||||
alt="Success Icon"
|
alt="Success Icon"
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</MainButton>
|
</MainButton>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
{(error || apiError) && (
|
{(error || apiError || errorSinglePayment?.error) && (
|
||||||
<Title variant="h3" style={{ color: "red", margin: 0 }}>
|
<Title variant="h3" style={{ color: "red", margin: 0 }}>
|
||||||
Something went wrong
|
Something went wrong:{" "}
|
||||||
|
{errorSinglePayment?.error?.length && errorSinglePayment?.error}
|
||||||
</Title>
|
</Title>
|
||||||
)}
|
)}
|
||||||
{apiError && (
|
{apiError && (
|
||||||
|
|||||||
35
src/data/products.ts
Normal file
35
src/data/products.ts
Normal file
@ -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(),
|
||||||
|
},
|
||||||
|
};
|
||||||
156
src/hooks/payment/useSinglePayment.ts
Normal file
156
src/hooks/payment/useSinglePayment.ts
Normal file
@ -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<SinglePayment.ResponsePost>();
|
||||||
|
const [product, setProduct] = useState<SinglePayment.ResponseGet>();
|
||||||
|
const [error, setError] = useState<IErrorSinglePayment>(
|
||||||
|
{} 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]
|
||||||
|
);
|
||||||
|
};
|
||||||
@ -8,7 +8,6 @@ export const useSchemeColorByElement = (
|
|||||||
) => {
|
) => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const pageElement = element?.querySelectorAll(searchSelectors)[0];
|
const pageElement = element?.querySelectorAll(searchSelectors)[0];
|
||||||
console.log("pageElement", pageElement);
|
|
||||||
|
|
||||||
const scheme = document.querySelector('meta[name="theme-color"]');
|
const scheme = document.querySelector('meta[name="theme-color"]');
|
||||||
if (scheme && !pageElement) {
|
if (scheme && !pageElement) {
|
||||||
@ -20,7 +19,6 @@ export const useSchemeColorByElement = (
|
|||||||
if (colorScheme?.a === 0) {
|
if (colorScheme?.a === 0) {
|
||||||
backgroundColor = "#ffffff";
|
backgroundColor = "#ffffff";
|
||||||
}
|
}
|
||||||
console.log("backgroundColor", backgroundColor);
|
|
||||||
if (scheme && backgroundColor.length) {
|
if (scheme && backgroundColor.length) {
|
||||||
return scheme.setAttribute("content", backgroundColor);
|
return scheme.setAttribute("content", backgroundColor);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -124,7 +124,7 @@ const routes = {
|
|||||||
|
|
||||||
// Advisors
|
// Advisors
|
||||||
advisors: () => [host, "advisors"].join("/"),
|
advisors: () => [host, "advisors"].join("/"),
|
||||||
advisorChat: (id: number) => [host, "advisors", id].join("/"),
|
advisorChat: (id: string) => [host, "advisors", id].join("/"),
|
||||||
// Email - Pay - Email
|
// Email - Pay - Email
|
||||||
epeGender: () => [host, "epe", "gender"].join("/"),
|
epeGender: () => [host, "epe", "gender"].join("/"),
|
||||||
epeBirthdate: () => [host, "epe", "birthdate"].join("/"),
|
epeBirthdate: () => [host, "epe", "birthdate"].join("/"),
|
||||||
@ -132,8 +132,26 @@ const routes = {
|
|||||||
epeSuccessPayment: () => [host, "epe", "success-payment"].join("/"),
|
epeSuccessPayment: () => [host, "epe", "success-payment"].join("/"),
|
||||||
epeFailPayment: () => [host, "epe", "fail-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("/"),
|
getInformationPartner: () => [host, "get-information-partner"].join("/"),
|
||||||
|
|
||||||
|
loadingPage: () => [host, "loading-page"].join("/"),
|
||||||
notFound: () => [host, "404"].join("/"),
|
notFound: () => [host, "404"].join("/"),
|
||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
@ -194,6 +212,10 @@ const routes = {
|
|||||||
dApiTestPaymentProducts: () =>
|
dApiTestPaymentProducts: () =>
|
||||||
[dApiHost, "payment", "test", "products"].join("/"),
|
[dApiHost, "payment", "test", "products"].join("/"),
|
||||||
dApiPaymentCheckout: () => [dApiHost, "payment", "checkout"].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("/"),
|
assistants: () => [apiHost, prefix, "ai", "assistants.json"].join("/"),
|
||||||
setExternalChatIdAssistants: (chatId: string) =>
|
setExternalChatIdAssistants: (chatId: string) =>
|
||||||
@ -230,6 +252,9 @@ export const entrypoints = [
|
|||||||
routes.client.trialChoice(),
|
routes.client.trialChoice(),
|
||||||
routes.client.palmistry(),
|
routes.client.palmistry(),
|
||||||
routes.client.advisors(),
|
routes.client.advisors(),
|
||||||
|
routes.client.advisorChatGender(),
|
||||||
|
routes.client.advisorChatSuccessPayment(),
|
||||||
|
routes.client.advisorChatFailPayment(),
|
||||||
];
|
];
|
||||||
export const isEntrypoint = (path: string) => entrypoints.includes(path);
|
export const isEntrypoint = (path: string) => entrypoints.includes(path);
|
||||||
export const isNotEntrypoint = (path: string) => !isEntrypoint(path);
|
export const isNotEntrypoint = (path: string) => !isEntrypoint(path);
|
||||||
@ -318,11 +343,13 @@ export const withoutFooterRoutes = [
|
|||||||
routes.client.advisors(),
|
routes.client.advisors(),
|
||||||
routes.client.epeSuccessPayment(),
|
routes.client.epeSuccessPayment(),
|
||||||
routes.client.getInformationPartner(),
|
routes.client.getInformationPartner(),
|
||||||
|
routes.client.advisorChatBirthPlace(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const withoutFooterPartOfRoutes = [
|
export const withoutFooterPartOfRoutes = [
|
||||||
routes.client.questionnaire(),
|
routes.client.questionnaire(),
|
||||||
routes.client.advisors(),
|
routes.client.advisors(),
|
||||||
|
routes.client.advisorChatPrivate(),
|
||||||
];
|
];
|
||||||
|
|
||||||
export const hasNoFooter = (path: string) => {
|
export const hasNoFooter = (path: string) => {
|
||||||
@ -393,6 +420,7 @@ export const withoutHeaderRoutes = [
|
|||||||
routes.client.advisors(),
|
routes.client.advisors(),
|
||||||
routes.client.epeSuccessPayment(),
|
routes.client.epeSuccessPayment(),
|
||||||
routes.client.getInformationPartner(),
|
routes.client.getInformationPartner(),
|
||||||
|
routes.client.advisorChatPrivate(),
|
||||||
];
|
];
|
||||||
export const hasNoHeader = (path: string) => {
|
export const hasNoHeader = (path: string) => {
|
||||||
let result = true;
|
let result = true;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user