This commit is contained in:
Daniil Chemerkin 2025-01-23 11:06:28 +00:00
parent 5c13b4727c
commit c9eacda411
22 changed files with 331 additions and 69 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

View File

@ -41,6 +41,7 @@ const api = {
// getElement: createMethod<Element.Payload, Element.Response>(Element.createRequest), // getElement: createMethod<Element.Payload, Element.Response>(Element.createRequest),
getElements: createMethod<Elements.Payload, Elements.Response>(Elements.createRequest), getElements: createMethod<Elements.Payload, Elements.Response>(Elements.createRequest),
getUser: createMethod<User.GetPayload, User.Response>(User.createGetRequest), getUser: createMethod<User.GetPayload, User.Response>(User.createGetRequest),
getMe: createMethod<User.GetPayload, User.IMeResponse>(User.createMeRequest),
updateUser: createMethod<User.PatchPayload, User.Response>(User.createPatchRequest), updateUser: createMethod<User.PatchPayload, User.Response>(User.createPatchRequest),
getAssets: createMethod<Assets.Payload, Assets.Response>(Assets.createRequest), getAssets: createMethod<Assets.Payload, Assets.Response>(Assets.createRequest),
getAssetCategories: createMethod<AssetCategories.Payload, AssetCategories.Response>(AssetCategories.createRequest), getAssetCategories: createMethod<AssetCategories.Payload, AssetCategories.Response>(AssetCategories.createRequest),
@ -83,6 +84,7 @@ const api = {
// Payment // Payment
makePayment: createMethod<Payment.PayloadPost, Payment.ResponsePost>(Payment.createRequestPost), makePayment: createMethod<Payment.PayloadPost, Payment.ResponsePost>(Payment.createRequestPost),
getPaymentConfig: createMethod<null, Payment.IPaymentConfigResponse>(Payment.getConfigRequest), getPaymentConfig: createMethod<null, Payment.IPaymentConfigResponse>(Payment.getConfigRequest),
getPaymentMethods: createMethod<Payment.Payload, Payment.IPaymentMethodsResponse>(Payment.getMethodsRequest),
// User videos // User videos
getUserVideos: createMethod<UserVideos.PayloadGet, UserVideos.ResponseGet>(UserVideos.createRequest), getUserVideos: createMethod<UserVideos.PayloadGet, UserVideos.ResponseGet>(UserVideos.createRequest),
// User PDF // User PDF

View File

@ -1,7 +1,7 @@
import routes from "@/routes"; import routes from "@/routes";
import { getAuthHeaders, getBaseHeaders } from "../utils"; import { getAuthHeaders, getBaseHeaders } from "../utils";
interface Payload { export interface Payload {
token: string; token: string;
} }
@ -33,12 +33,19 @@ interface ResponsePostSuccess {
} }
} }
interface ResponsePostSinglePaymentSuccess {
payment: {
status: string;
invoiceId: string;
};
}
interface ResponsePostError { interface ResponsePostError {
status: string; status: string;
message: string; message: string;
} }
export type ResponsePost = ResponsePostSuccess | ResponsePostError; export type ResponsePost = ResponsePostSuccess | ResponsePostSinglePaymentSuccess | ResponsePostError;
export const createRequestPost = ({ token, productId, placementId, paywallId, paymentToken }: PayloadPost): Request => { export const createRequestPost = ({ token, productId, placementId, paywallId, paymentToken }: PayloadPost): Request => {
const url = new URL(routes.server.makePayment()); const url = new URL(routes.server.makePayment());
@ -64,3 +71,13 @@ export const getConfigRequest = (): Request => {
const url = new URL(routes.server.getPaymentConfig()); const url = new URL(routes.server.getPaymentConfig());
return new Request(url, { method: "GET", headers: getBaseHeaders() }); return new Request(url, { method: "GET", headers: getBaseHeaders() });
}; };
export interface IPaymentMethodsResponse {
status: "success" | "error",
message: string,
}
export const getMethodsRequest = ({ token }: Payload): Request => {
const url = new URL(routes.server.getPaymentMethods());
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
};

View File

@ -194,3 +194,57 @@ export const createAuthorizeRequest = (data: ICreateAuthorizePayload): Request =
body, body,
}); });
} }
export interface IMeResponse {
user: IUser;
}
export interface IUser {
ipLookup?: {
country: string;
region: string;
eu: string;
timezone: string;
city: string;
},
profile: {
birthplace: {
address: string;
},
name: string;
birthdate: string;
gender: string;
age: number;
sign: string;
},
partner?: {
birthplace: {
address: string;
},
birthdate: string;
gender: string;
age: number;
sign: string;
},
_id: string;
initialIp?: string;
sessionId?: string;
email: string;
locale: string;
timezone: string;
source: string;
sign: boolean;
signDate: string;
password: string;
externalId: string;
klaviyoId: string;
stripeId: string | null;
assistants: string[];
createdAt: string;
updatedAt: string;
}
export const createMeRequest = ({ token }: GetPayload): Request => {
const url = new URL(routes.server.me());
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
}

View File

@ -103,7 +103,6 @@ import AdditionalDiscount from "../pages/AdditionalDiscount";
import TrialPaymentWithDiscount from "../pages/TrialPaymentWithDiscount"; import TrialPaymentWithDiscount from "../pages/TrialPaymentWithDiscount";
import MarketingLanding from "../pages/EmailLetters/MarketingLanding"; import MarketingLanding from "../pages/EmailLetters/MarketingLanding";
import MarketingTrialPayment from "../pages/EmailLetters/MarketingTrialPayment"; import MarketingTrialPayment from "../pages/EmailLetters/MarketingTrialPayment";
import { ScrollToTop } from "@/hooks/scrollToTop";
import { EUserDeviceType } from "@/store/userConfig"; import { EUserDeviceType } from "@/store/userConfig";
import TryAppPage from "../pages/TryApp"; import TryAppPage from "../pages/TryApp";
import AdditionalPurchases from "../pages/AdditionalPurchases"; import AdditionalPurchases from "../pages/AdditionalPurchases";
@ -135,6 +134,7 @@ import ChatsRoutes from "@/routerComponents/Chats";
import CookieYesController from "@/routerComponents/CookieYesController"; import CookieYesController from "@/routerComponents/CookieYesController";
import PalmistryV2Routes from "@/routerComponents/Palmistry/v2"; import PalmistryV2Routes from "@/routerComponents/Palmistry/v2";
import MarketingLandingV1Routes from "@/routerComponents/MarketingLanding/v1"; import MarketingLandingV1Routes from "@/routerComponents/MarketingLanding/v1";
import { useScrollToTop } from "@/hooks/useScrollToTop";
const isProduction = import.meta.env.MODE === "production"; const isProduction = import.meta.env.MODE === "production";
@ -145,6 +145,7 @@ if (isProduction) {
function App(): JSX.Element { function App(): JSX.Element {
const location = useLocation(); const location = useLocation();
const [leoApng, setLeoApng] = useState<Error | APNG>(Error); const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
useScrollToTop({ scrollBehavior: "auto" });
// const [ // const [
// padLockApng, // padLockApng,
// setPadLockApng, // setPadLockApng,
@ -240,7 +241,22 @@ function App(): JSX.Element {
try { try {
const { token } = await api.getRealToken({ token: jwtToken }); const { token } = await api.getRealToken({ token: jwtToken });
const { user } = await api.getUser({ token }); const { user } = await api.getUser({ token });
const { user: userMe } = await api.getMe({ token });
signUp(token, user); signUp(token, user);
dispatch(actions.questionnaire.update({
gender: userMe.profile.gender ?? undefined,
birthPlace: userMe.profile.birthplace?.address ?? undefined,
birthdate: userMe.profile.birthdate ?? undefined,
partnerBirthPlace: userMe.partner?.birthplace?.address ?? undefined,
partnerBirthdate: userMe.partner?.birthdate ?? undefined,
partnerGender: userMe.partner?.gender ?? undefined,
}))
dispatch(actions.user.update({
username: userMe.profile.name ?? undefined,
}));
} catch (error) { } catch (error) {
console.log("Error of get real token or get user: "); console.log("Error of get real token or get user: ");
console.error(error); console.error(error);
@ -1053,14 +1069,13 @@ function Layout(): JSX.Element {
return ( return (
<div className="container"> <div className="container">
<ScrollToTop />
{showHeader ? ( {showHeader ? (
<Header <Header
openMenu={() => setIsMenuOpen(true)} openMenu={() => setIsMenuOpen(true)}
/> />
) : null} ) : null}
{isRouteFullDataModal && ( {isRouteFullDataModal && (
<Modal open={isShowFullDataModal} isCloseButtonVisible={false}> <Modal open={isShowFullDataModal} isCloseButtonVisible={false} onClose={() => { }}>
<FullDataModal onClose={onCloseFullDataModal} /> <FullDataModal onClose={onCloseFullDataModal} />
</Modal> </Modal>
)} )}
@ -1250,7 +1265,7 @@ export function PrivateOutlet(): JSX.Element {
function PrivateSubscriptionOutlet(): JSX.Element { function PrivateSubscriptionOutlet(): JSX.Element {
const isProduction = import.meta.env.MODE === "production"; const isProduction = import.meta.env.MODE === "production";
const status = useSelector(selectors.selectStatus); const status = useSelector(selectors.selectStatus);
return status === "subscribed" || !isProduction || true ? ( return status === "subscribed" || !isProduction ? (
<Outlet /> <Outlet />
) : ( ) : (
<Navigate to={getRouteBy(status)} replace={true} /> <Navigate to={getRouteBy(status)} replace={true} />

View File

@ -19,19 +19,20 @@ import { Products, useApi, useApiCall } from "@/api";
import { usePaywall } from "@/hooks/paywall/usePaywall"; import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall"; import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { useAuth } from "@/auth"; import { useAuth } from "@/auth";
import { ResponsePost } from "@/api/resources/SinglePayment";
import { createSinglePayment } from "@/services/singlePayment"; import { createSinglePayment } from "@/services/singlePayment";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import Title from "@/components/Title"; import Title from "@/components/Title";
import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm"; // import PaymentForm from "@/components/pages/SinglePaymentPage/PaymentForm";
import { getPriceCentsToDollars } from "@/services/price"; import { getPriceCentsToDollars } from "@/services/price";
import { IMessage } from "@/api/resources/ChatMessages"; import { IMessage } from "@/api/resources/ChatMessages";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
const returnUrl = `${window.location.protocol}//${ const returnUrl = `${window.location.protocol}//${window.location.host
window.location.host }${routes.client.chatsExpert()}`;
}${routes.client.chatsExpert()}`;
const placementKey = EPlacementKeys["aura.placement.chat"];
function ExpertChat() { function ExpertChat() {
const { translate } = useTranslations(ELocalesPlacement.Chats); const { translate } = useTranslations(ELocalesPlacement.Chats);
@ -66,12 +67,16 @@ function ExpertChat() {
// Payment // Payment
const { user: userFromStore } = useAuth(); const { user: userFromStore } = useAuth();
const tokenFromStore = useSelector(selectors.selectToken); const tokenFromStore = useSelector(selectors.selectToken);
const [paymentIntent, setPaymentIntent] = useState<ResponsePost | null>(null);
const [isLoadingPayment, setIsLoadingPayment] = useState(false); const [isLoadingPayment, setIsLoadingPayment] = useState(false);
const [isError, setIsError] = useState(false); const [isError, setIsError] = useState(false);
const [currentProduct, setCurrentProduct] = useState<IPaywallProduct | null>( // const [currentProduct, setCurrentProduct] = useState<IPaywallProduct | null>(
null // null
); // );
const currentProduct = useSelector(selectors.selectActiveProduct);
const setCurrentProduct = (product: IPaywallProduct) => {
dispatch(actions.payment.update({ activeProduct: product }));
};
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
const isPayedFirstPurchase = useSelector( const isPayedFirstPurchase = useSelector(
selectors.selectIsPayedFirstPurchase selectors.selectIsPayedFirstPurchase
@ -101,7 +106,7 @@ function ExpertChat() {
>(checkIsPayedFirstPurchase); >(checkIsPayedFirstPurchase);
const { products } = usePaywall({ const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.chat"], placementKey,
}); });
const scrollToBottom = () => { const scrollToBottom = () => {
@ -182,6 +187,13 @@ function ExpertChat() {
if (!currentProduct) return; if (!currentProduct) return;
setCurrentProduct(currentProduct); setCurrentProduct(currentProduct);
setIsLoadingPayment(true); setIsLoadingPayment(true);
const isPaymentMethodExist = await api.getPaymentMethods({ token: tokenFromStore });
if (isPaymentMethodExist.status === "error") {
return setIsPaymentModalOpen(true);
}
// if (!isPayedFirstPurchase) {
// return setIsPaymentModalOpen(true);
// }
const { _id, key } = currentProduct; const { _id, key } = currentProduct;
const paymentInfo = { const paymentInfo = {
productId: _id, productId: _id,
@ -197,7 +209,7 @@ function ExpertChat() {
returnUrl, returnUrl,
api api
); );
setPaymentIntent(paymentIntent); // setPaymentIntent(paymentIntent);
if ("payment" in paymentIntent) { if ("payment" in paymentIntent) {
if (paymentIntent.payment.status === "paid") return closeModals(); if (paymentIntent.payment.status === "paid") return closeModals();
return setIsError(true); return setIsError(true);
@ -249,27 +261,41 @@ function ExpertChat() {
); );
}; };
const onPaymentError = () => {
setIsPaymentModalOpen(false);
return setIsError(true);
}
const onPaymentSuccess = () => {
setIsPaymentModalOpen(false);
setIsLoadingPayment(false);
return closeModals();
}
return ( return (
<section className={`${styles.page} page`}> <section className={`${styles.page} page`}>
{!isLoading && {!isLoading &&
paymentIntent &&
"paymentIntent" in paymentIntent &&
!!tokenFromStore.length && !!tokenFromStore.length &&
currentProduct && ( currentProduct && (
<> <>
<Modal <Modal
open={!!paymentIntent} open={isPaymentModalOpen}
onClose={() => setPaymentIntent(null)} onClose={() => setIsPaymentModalOpen(false)}
containerClassName={styles.modal} containerClassName={styles.modal}
> >
<Title variant="h1" className={styles["modal-title"]}> <Title variant="h1" className={styles["modal-title"]}>
{getPriceCentsToDollars(currentProduct.price || 0)}$ {getPriceCentsToDollars(currentProduct.price || 0)}$
</Title> </Title>
<PaymentForm {/* <PaymentForm
isLoadingPayment={isLoadingPayment} isLoadingPayment={isLoadingPayment}
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}
/> */}
<PaymentForm
placementKey={placementKey}
onPaymentError={onPaymentError}
onPaymentSuccess={onPaymentSuccess}
/> />
</Modal> </Modal>
</> </>

View File

@ -1,3 +1,4 @@
import { images } from '../../data';
import styles from './styles.module.scss'; import styles from './styles.module.scss';
interface CustomerCounterProps { interface CustomerCounterProps {
@ -7,7 +8,7 @@ interface CustomerCounterProps {
function CustomerCounter({ count }: CustomerCounterProps) { function CustomerCounter({ count }: CustomerCounterProps) {
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.circularText}> {/* <div className={styles.circularText}>
<svg viewBox="0 0 100 100"> <svg viewBox="0 0 100 100">
<path <path
id="curve" id="curve"
@ -25,7 +26,8 @@ function CustomerCounter({ count }: CustomerCounterProps) {
</textPath> </textPath>
</text> </text>
</svg> </svg>
</div> </div> */}
<img className={styles.circularText} src={images("circular-text.png")} alt="" />
<div className={styles.count}>{count}</div> <div className={styles.count}>{count}</div>
</div> </div>
); );

View File

@ -2,7 +2,7 @@
position: relative; position: relative;
width: 263px; width: 263px;
height: 263px; height: 263px;
background: #7A6BE2; // background: #7A6BE2;
border-radius: 50%; border-radius: 50%;
display: flex; display: flex;
align-items: center; align-items: center;
@ -16,8 +16,8 @@
.circularText { .circularText {
position: absolute; position: absolute;
width: 100%; width: calc(100% + 24px);
height: 100%; height: calc(100% + 24px);
animation: rotate 20s linear infinite; animation: rotate 20s linear infinite;
-webkit-transform: translateZ(0); -webkit-transform: translateZ(0);
-webkit-perspective: 1000; -webkit-perspective: 1000;

View File

@ -1,4 +1,4 @@
import { ReactNode, useEffect } from "react"; import { ReactNode, useCallback, useEffect, useRef, useState } from "react";
import styles from "./styles.module.css"; import styles from "./styles.module.css";
interface ModalProps { interface ModalProps {
@ -8,7 +8,7 @@ interface ModalProps {
className?: string; className?: string;
containerClassName?: string; containerClassName?: string;
type?: "hidden" | "normal"; type?: "hidden" | "normal";
onClose?: () => void; onClose: () => void;
removeNoScroll?: boolean; removeNoScroll?: boolean;
} }
@ -22,6 +22,8 @@ function Modal({
onClose, onClose,
removeNoScroll = true removeNoScroll = true
}: ModalProps): JSX.Element { }: ModalProps): JSX.Element {
const modalContentRef = useRef<HTMLDivElement>(null);
const handleClose = (event: React.MouseEvent) => { const handleClose = (event: React.MouseEvent) => {
if (event.target !== event.currentTarget) return; if (event.target !== event.currentTarget) return;
document.body.classList.remove("no-scroll"); document.body.classList.remove("no-scroll");
@ -42,18 +44,53 @@ function Modal({
}; };
}, [open, removeNoScroll]); }, [open, removeNoScroll]);
const [position, setPosition] = useState({ top: 0, left: 0 });
const getModalContentPosition = useCallback(() => {
const modalContent = modalContentRef.current;
if (!modalContent) return {
top: 0,
left: 0
};
const { top, left } = modalContent.getBoundingClientRect();
return { top, left };
}, [modalContentRef]);
useEffect(() => {
const updatePosition = () => {
requestAnimationFrame(() => {
setPosition(getModalContentPosition());
});
};
if (open) {
updatePosition();
}
window.addEventListener('resize', updatePosition);
return () => {
window.removeEventListener('resize', updatePosition);
};
}, [getModalContentPosition, open]);
if (!open && type === "normal") return <></>; if (!open && type === "normal") return <></>;
return ( return (
<div <div
className={`${styles.modal} ${className} ${ className={`${styles.modal} ${className} ${type === "hidden" && !open ? styles.hidden : ""
type === "hidden" && !open ? styles.hidden : "" }`}
}`}
onClick={handleClose} onClick={handleClose}
> >
<div className={`${styles["modal-content"]} ${containerClassName}`}> {isCloseButtonVisible && (
{isCloseButtonVisible && ( <button className={styles["modal-close-btn"]} onClick={handleClose}
<button className={styles["modal-close-btn"]} onClick={handleClose} /> style={{
)} top: `${position.top + 16}px`,
left: `${position.left + 15}px`
}}
/>
)}
<div className={`${styles["modal-content"]} ${containerClassName}`} ref={modalContentRef}>
{children} {children}
</div> </div>
</div> </div>

View File

@ -53,6 +53,7 @@
background-position: center; background-position: center;
background-color: transparent; background-color: transparent;
background-image: url(./close.svg); background-image: url(./close.svg);
z-index: 4;
} }
.modal .main-btn { .modal .main-btn {

View File

@ -1,6 +1,6 @@
.footer { .footer {
width: 100%; width: 100%;
margin-top: 30px; margin: 30px 0 62px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;

View File

@ -100,7 +100,7 @@ function PaymentForm({
)} )}
> >
<Modal containerClassName={styles["modal-content"]} open={isPaymentModalOpen}> <Modal containerClassName={styles["modal-content"]} open={isPaymentModalOpen} onClose={() => setIsPaymentModalOpen(false)}>
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={EPlacementKeys['aura.placement.palmistry.redesign']} /> <NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={EPlacementKeys['aura.placement.palmistry.redesign']} />
</Modal> </Modal>

View File

@ -14,9 +14,11 @@ import { useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
import { usePreloadImages } from "@/hooks/preload/images"; import { usePreloadImages } from "@/hooks/preload/images";
import useTimer from "@/hooks/palmistry/use-timer";
function TrialPayment() { function TrialPayment() {
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1); const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
const time = useTimer();
const navigate = useNavigate(); const navigate = useNavigate();
usePreloadImages([ usePreloadImages([
"/v1/palmistry/ticket.svg", "/v1/palmistry/ticket.svg",
@ -66,6 +68,24 @@ function TrialPayment() {
</Title> </Title>
<img className={styles.partners} src={`${palmistryV1Prefix}/partners.png`} alt="Partners" /> <img className={styles.partners} src={`${palmistryV1Prefix}/partners.png`} alt="Partners" />
<Footer /> <Footer />
<div className={styles["paywall__get-prediction"]}>
<div>
{translate("/paywall.offer_reserved.title", undefined, ELocalesPlacement.PalmistryV0)}
<span className={styles["paywall__get-prediction-timer"]}>
<span>{time}</span>
</span>
</div>
<Button
type="button"
className={styles["paywall__get-prediction-button"]}
onClick={handleNext}
>
{translate("/paywall.offer_reserved.button", undefined, ELocalesPlacement.PalmistryV0)}
</Button>
</div>
</> </>
); );
} }

View File

@ -17,7 +17,7 @@
margin: 40px 18px 20px; margin: 40px 18px 20px;
font-weight: 700; font-weight: 700;
& > span { &>span {
color: #224e90; color: #224e90;
} }
} }
@ -32,7 +32,7 @@
font-size: 32px; font-size: 32px;
margin-top: 50px; margin-top: 50px;
& > span { &>span {
color: #224e90; color: #224e90;
} }
} }
@ -40,3 +40,49 @@
.partners { .partners {
width: 100%; width: 100%;
} }
.paywall__get-prediction {
position: fixed;
bottom: 0;
left: 0;
align-items: center;
background: #eff2fd;
box-shadow: 0 -3px 11px rgba(0, 0, 0, .15);
color: #4a567a;
display: flex;
font-size: 14px;
padding: 12px 24px;
transition: all 0.5s;
width: 100%;
z-index: 10;
}
.paywall__get-prediction-timer {
font-family: "SF Mono Bold, sans-serif";
border-radius: 4px;
background: initial;
display: inline;
margin: 5px;
min-width: 62px;
padding: 0;
}
.paywall__get-prediction-timer>span {
color: #066fde;
font-size: 14px;
font-weight: 700;
line-height: 22px;
}
.paywall__get-prediction-button {
font-weight: 700;
font-size: 18px;
line-height: 26px;
min-height: auto;
min-width: auto;
padding: 6px 8px;
white-space: nowrap;
width: auto;
}

View File

@ -14,7 +14,7 @@ import { EPlacementKeys } from "@/api/resources/Paywall";
import { usePaywall } from "@/hooks/paywall/usePaywall"; import { usePaywall } from "@/hooks/paywall/usePaywall";
import MoneyBackGuarantee from "../../components/MoneyBackGuarantee"; import MoneyBackGuarantee from "../../components/MoneyBackGuarantee";
import PalmsSayAbout from "../../components/PalmsSayAbout"; import PalmsSayAbout from "../../components/PalmsSayAbout";
import { useEffect, useMemo, useState } from "react"; import { useCallback, useEffect, useMemo, useState } from "react";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import WithPartnerInformation from "../../components/WithPartnerInformation"; import WithPartnerInformation from "../../components/WithPartnerInformation";
import PersonalInformation from "../../components/PersonalInformation"; import PersonalInformation from "../../components/PersonalInformation";
@ -22,10 +22,13 @@ import Reviews from "../../components/Reviews";
import Address from "../../components/Address"; import Address from "../../components/Address";
import Modal from "@/components/Modal"; import Modal from "@/components/Modal";
import PaymentForm from "@/components/Payment/nmi/PaymentForm"; import PaymentForm from "@/components/Payment/nmi/PaymentForm";
import { useApi, useApiCall, User } from "@/api";
const placementKey = EPlacementKeys["aura.placement.email.palmistry"]; const placementKey = EPlacementKeys["aura.placement.email.palmistry"];
function TrialPayment() { function TrialPayment() {
const api = useApi();
const token = useSelector(selectors.selectToken);
const dispatch = useDispatch(); const dispatch = useDispatch();
// const { translate } = useTranslations(ELocalesPlacement.PalmistryV1); // const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
const { products } = usePaywall({ const { products } = usePaywall({
@ -36,16 +39,15 @@ function TrialPayment() {
const trialDuration = activeProduct?.trialDuration || 7; const trialDuration = activeProduct?.trialDuration || 7;
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const { const {
gender, gender,
birthPlace, birthPlace,
partnerBirthPlace, partnerBirthPlace,
partnerBirthdate, partnerBirthdate,
partnerGender, partnerGender,
flowChoice, birthdate,
} = useSelector(selectors.selectQuestionnaire) } = useSelector(selectors.selectQuestionnaire)
const zodiacSign = getZodiacSignByDate(birthdate);
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate); const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
const navigate = useNavigate(); const navigate = useNavigate();
@ -72,12 +74,16 @@ function TrialPayment() {
setIsPaymentModalOpen(true); setIsPaymentModalOpen(true);
}; };
const userData = useCallback(async () => {
const { user } = await api.getMe({ token: token });
return user;
}, [api]);
const { data: user } = useApiCall<User.IUser>(userData);
const singleOrWithPartner = useMemo(() => { const singleOrWithPartner = useMemo(() => {
if (["relationship", "married"].includes(flowChoice)) { return user?.partner ? "partner" : "single";
return "partner"; }, [user]);
}
return "single";
}, [flowChoice]);
useEffect(() => { useEffect(() => {
if (!activeProduct) return; if (!activeProduct) return;

View File

@ -127,8 +127,13 @@ export const usePayment = ({
paymentToken: response.token paymentToken: response.token
}); });
setPaymentResponse(res); setPaymentResponse(res);
setIsPaymentSuccess(res.status === "paid"); if ("payment" in res) {
if (res.status !== "paid") { setIsPaymentSuccess(res.payment.status === "paid");
} else {
setIsPaymentSuccess(res.status === "paid");
}
const status = "payment" in res ? res.payment.status : res.status;
if (status !== "paid") {
setError("message" in res ? res.message : "Something went wrong") setError("message" in res ? res.message : "Something went wrong")
} }
} catch (error: any) { } catch (error: any) {

View File

@ -39,7 +39,11 @@ export const useMakePayment = ({
paywallId paywallId
}); });
if (res.status === "paid") { if ("payment" in res && res.payment.status === "paid") {
return window.location.href = `${returnPaidUrl}?redirect_status=succeeded`;
}
if ("status" in res && res.status === "paid") {
return window.location.href = `${returnPaidUrl}?redirect_status=succeeded`; return window.location.href = `${returnPaidUrl}?redirect_status=succeeded`;
} }

View File

@ -1,16 +0,0 @@
import { useEffect } from "react";
import { useLocation } from "react-router-dom";
export const ScrollToTop = () => {
const { pathname } = useLocation();
useEffect(() => {
window.scrollTo({
top: 0,
left: 0,
behavior: "smooth",
});
}, [pathname]);
return null;
};

View File

@ -0,0 +1,24 @@
import { useLayoutEffect } from 'react';
import { useLocation } from 'react-router-dom';
interface ScrollToTopProps {
scrollBehavior?: ScrollBehavior;
element?: HTMLElement | null | Window;
}
export const useScrollToTop = ({
scrollBehavior = 'auto',
element = window
}: ScrollToTopProps = {}) => {
const { pathname } = useLocation();
useLayoutEffect(() => {
if (!element) return;
element.scrollTo({
top: 0,
left: 0,
behavior: scrollBehavior
});
}, [pathname]);
};

View File

@ -190,7 +190,7 @@ div[class^="divider"] {
#root { #root {
height: 100%; height: 100%;
min-width: 100vw; min-width: 100vw;
overflow: auto; /* overflow: auto; */
} }
a, a,

View File

@ -303,6 +303,8 @@ const routes = {
server: { server: {
userLocale: () => ["https://ipapi.co", "json"].join("/"), userLocale: () => ["https://ipapi.co", "json"].join("/"),
user: () => [apiHost, prefix, "user.json"].join("/"), user: () => [apiHost, prefix, "user.json"].join("/"),
// new method for getting user data
me: () => [dApiHost, "users", "me"].join("/"),
// token: () => [apiHost, prefix, "auth", "token.json"].join("/"), // token: () => [apiHost, prefix, "auth", "token.json"].join("/"),
elements: () => [oldBackendPrefix, "elements.json"].join("/"), elements: () => [oldBackendPrefix, "elements.json"].join("/"),
zodiacs: (zodiac: string) => zodiacs: (zodiac: string) =>
@ -382,6 +384,8 @@ const routes = {
// Payment // Payment
makePayment: () => [dApiHost, dApiPrefix, "payment", "checkout"].join("/"), makePayment: () => [dApiHost, dApiPrefix, "payment", "checkout"].join("/"),
getPaymentConfig: () => [dApiHost, dApiPrefix, "payment", "config"].join("/"), getPaymentConfig: () => [dApiHost, dApiPrefix, "payment", "config"].join("/"),
// check payment method exist
getPaymentMethods: () => [dApiHost, dApiPrefix, "payment", "method"].join("/"),
// User videos // User videos
getUserVideos: () => [dApiHost, "users", "videos", "combined"].join("/"), getUserVideos: () => [dApiHost, "users", "videos", "combined"].join("/"),

View File

@ -144,6 +144,18 @@ t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script', s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js'); 'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '1218510985903341'); fbq('init', '1218510985903341');
fbq('track', 'PageView');`;
const FBScriptCompatibilityFR = `
!function(f,b,e,v,n,t,s)
{if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};
if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];
s.parentNode.insertBefore(t,s)}(window, document,'script',
'https://connect.facebook.net/en_US/fbevents.js');
fbq('init', '923313529582091');
fbq('track', 'PageView');`; fbq('track', 'PageView');`;
// Chats // Chats
@ -187,6 +199,9 @@ fbq('track', 'PageView');`;
{!isPalmistry && !isChats && locale === "en" && ( {!isPalmistry && !isChats && locale === "en" && (
<script>{FBScriptCompatibilityEN}</script> <script>{FBScriptCompatibilityEN}</script>
)} )}
{!isPalmistry && !isChats && locale === "fr" && (
<script>{FBScriptCompatibilityFR}</script>
)}
{/* Chats */} {/* Chats */}
{isChats && locale === "en" && <script>{FBScriptChatsEN}</script>} {isChats && locale === "en" && <script>{FBScriptChatsEN}</script>}
</Helmet> </Helmet>