Merge branch 'develop' into 'main'
develop See merge request witapp/aura-webapp!543
This commit is contained in:
commit
243187592c
BIN
public/v1/email-marketing/circular-text.png
Normal file
BIN
public/v1/email-marketing/circular-text.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 51 KiB |
@ -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
|
||||||
|
|||||||
@ -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) });
|
||||||
|
};
|
||||||
|
|||||||
@ -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) });
|
||||||
|
}
|
||||||
|
|||||||
@ -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} />
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
@ -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>
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
}
|
||||||
@ -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;
|
||||||
|
|||||||
@ -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) {
|
||||||
|
|||||||
@ -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`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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;
|
|
||||||
};
|
|
||||||
24
src/hooks/useScrollToTop.tsx
Normal file
24
src/hooks/useScrollToTop.tsx
Normal 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]);
|
||||||
|
};
|
||||||
@ -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,
|
||||||
|
|||||||
@ -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("/"),
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user