Merge branch 'develop' into 'main'
develop See merge request witapp/aura-webapp!615
This commit is contained in:
commit
a5006f4899
@ -35,7 +35,28 @@
|
||||
"/gender": {
|
||||
"title": "What is Your Gender?",
|
||||
"description": "In palmistry, everyone has both masculine and feminine traits. <br><br> Let's determine yours for a more accurate palm reading.",
|
||||
"already_have_account": "Already have an account? Sign in"
|
||||
"already_have_account": "Already have an account? Sign in",
|
||||
"v1": {
|
||||
"title": "Тест на Совместимость<br>👩❤️👨 ",
|
||||
"subtitle": "Все начинается с тебя!<br>Выбери свой пол 👇",
|
||||
"points": {
|
||||
"point1": "тест займет не более 1 мин",
|
||||
"point2": "ты получишь анализ совместимости связанный с линиям на твоей руке",
|
||||
"point3": "100% достоверность данных",
|
||||
"point4": "более 50 стр разбора"
|
||||
}
|
||||
},
|
||||
"v2": {
|
||||
"title": "Тест на Совместимость",
|
||||
"subtitle": "Все начинается с тебя! Выбери свой пол.",
|
||||
"points": {
|
||||
"point1": "Тест займет не более 1 мин.",
|
||||
"point2": "Ты получишь разбор совместимости по хиромантическому анализу линий на твоей руке.",
|
||||
"point3": "Решишь проблемы в отношениях за месяц.",
|
||||
"point4": "Сэкономите сотни долларов на ненадёжных прогнозах.",
|
||||
"point5": "Получите персональный анализ."
|
||||
}
|
||||
}
|
||||
},
|
||||
"/birthdate": {
|
||||
"title": "When Were You Born?",
|
||||
|
||||
@ -83,6 +83,7 @@ const api = {
|
||||
getPaywallByPlacementKey: createMethod<Paywall.PayloadGet, Paywall.ResponseGet>(Paywall.createRequestGet),
|
||||
// Payment
|
||||
makePayment: createMethod<Payment.PayloadPost, Payment.ResponsePost>(Payment.createRequestPost),
|
||||
makeAnonymousPayment: createMethod<Payment.PayloadPostAnonymous, Payment.ResponsePostAnonymousSuccess>(Payment.createRequestPostAnonymous),
|
||||
getPaymentConfig: createMethod<null, Payment.IPaymentConfigResponse>(Payment.getConfigRequest),
|
||||
getPaymentMethods: createMethod<Payment.Payload, Payment.IPaymentMethodsResponse>(Payment.getMethodsRequest),
|
||||
// User videos
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import routes from "@/routes";
|
||||
import { getAuthHeaders, getBaseHeaders } from "../utils";
|
||||
import { ICreateAuthorizeResponse } from "./User";
|
||||
|
||||
export interface Payload {
|
||||
token: string;
|
||||
@ -12,6 +13,14 @@ export interface PayloadPost extends Payload {
|
||||
paymentToken?: string;
|
||||
}
|
||||
|
||||
export interface PayloadPostAnonymous {
|
||||
productId: string;
|
||||
placementId: string;
|
||||
paywallId: string;
|
||||
paymentToken: string;
|
||||
sessionId: string;
|
||||
}
|
||||
|
||||
interface ResponsePostSuccess {
|
||||
status: "payment_intent_created" | "paid" | unknown,
|
||||
type: "setup" | "payment",
|
||||
@ -47,6 +56,12 @@ interface ResponsePostError {
|
||||
|
||||
export type ResponsePost = ResponsePostSuccess | ResponsePostSinglePaymentSuccess | ResponsePostError;
|
||||
|
||||
export interface ResponsePostAnonymousSuccess {
|
||||
user: ICreateAuthorizeResponse;
|
||||
status: string;
|
||||
invoiceId: string;
|
||||
}
|
||||
|
||||
export const createRequestPost = ({ token, productId, placementId, paywallId, paymentToken }: PayloadPost): Request => {
|
||||
const url = new URL(routes.server.makePayment());
|
||||
const body = JSON.stringify({
|
||||
@ -58,6 +73,18 @@ export const createRequestPost = ({ token, productId, placementId, paywallId, pa
|
||||
return new Request(url, { method: "POST", headers: getAuthHeaders(token), body });
|
||||
};
|
||||
|
||||
export const createRequestPostAnonymous = ({ productId, placementId, paywallId, paymentToken, sessionId }: PayloadPostAnonymous): Request => {
|
||||
const url = new URL(routes.server.makeAnonymousPayment());
|
||||
const body = JSON.stringify({
|
||||
productId,
|
||||
placementId,
|
||||
paywallId,
|
||||
paymentToken,
|
||||
sessionId
|
||||
});
|
||||
return new Request(url, { method: "POST", headers: getBaseHeaders(), body });
|
||||
};
|
||||
|
||||
export interface IPaymentConfigResponse {
|
||||
status: "success" | string,
|
||||
data: {
|
||||
|
||||
@ -23,7 +23,8 @@ export enum EPlacementKeys {
|
||||
"aura.placement.email.compatibility.discount" = "aura.placement.email.compatibility.discount",
|
||||
"aura.placement.palmistry.secret.discount" = "aura.placement.palmistry.secret.discount",
|
||||
"aura.placement.compatibility.v2" = "aura.placement.compatibility.v2",
|
||||
"aura.placement.compatibility.v2.secret.discount" = "aura.placement.compatibility.v2.secret.discount"
|
||||
"aura.placement.compatibility.v2.secret.discount" = "aura.placement.compatibility.v2.secret.discount",
|
||||
"aura.placement.payment" = "aura.placement.payment" // anonymous payment
|
||||
}
|
||||
|
||||
export interface ResponseGetSuccess {
|
||||
|
||||
@ -139,7 +139,8 @@ export enum ESourceAuthorization {
|
||||
"aura.main.new" = "aura.main.new",
|
||||
"aura.palmistry.new" = "aura.palmistry.new",
|
||||
"aura.chats" = "aura.chats",
|
||||
"aura.compatibility.v2" = "aura.compatibility.v2"
|
||||
"aura.compatibility.v2" = "aura.compatibility.v2",
|
||||
"aura.test.payment" = "aura.test.payment" // anonymous payment
|
||||
}
|
||||
|
||||
export enum EGender {
|
||||
|
||||
89
src/components/Anonymous/pages/Payment/index.tsx
Normal file
89
src/components/Anonymous/pages/Payment/index.tsx
Normal file
@ -0,0 +1,89 @@
|
||||
import PaymentPage from "@/components/Payment/nmi/PaymentPage";
|
||||
import styles from "./styles.module.scss";
|
||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||
import routes from "@/routes";
|
||||
import { actions, selectors } from "@/store";
|
||||
import { useEffect, useState } from "react";
|
||||
import { ESourceAuthorization } from "@/api/resources/User";
|
||||
import { useSession } from "@/hooks/session/useSession";
|
||||
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||
import { useLocation, useNavigate } from "react-router-dom";
|
||||
import { useDispatch, useSelector } from "react-redux";
|
||||
import Loader, { LoaderColor } from "@/components/Loader";
|
||||
|
||||
function AnonymousPaymentPage() {
|
||||
const dispatch = useDispatch();
|
||||
const navigate = useNavigate();
|
||||
const { products } = usePaywall({
|
||||
placementKey: EPlacementKeys["aura.placement.payment"]
|
||||
});
|
||||
const activeProduct = products[0];
|
||||
const activeProductFromStore = useSelector(selectors.selectActiveProduct)
|
||||
const { session, createSession } = useSession();
|
||||
const utm = useSelector(selectors.selectUTM);
|
||||
const feature = useSelector(selectors.selectFeature);
|
||||
const [isParametersInitialized, setIsParametersInitialized] = useState(false);
|
||||
|
||||
const location = useLocation();
|
||||
|
||||
useEffect(() => {
|
||||
const _feature = location.pathname.replace(
|
||||
routes.client.anonymousPayment(),
|
||||
""
|
||||
);
|
||||
dispatch(
|
||||
actions.userConfig.setFeature(
|
||||
_feature.includes("/v1/gender") ? "" : _feature
|
||||
)
|
||||
);
|
||||
dispatch(actions.privacyPolicy.updateChecked(true))
|
||||
}, [dispatch, location.pathname]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isParametersInitialized) {
|
||||
return setIsParametersInitialized(true);
|
||||
}
|
||||
(async () => {
|
||||
await createSession(ESourceAuthorization["aura.test.payment"])
|
||||
})()
|
||||
}, [utm, feature, isParametersInitialized])
|
||||
|
||||
useEffect(() => {
|
||||
if (!!activeProduct) {
|
||||
dispatch(actions.payment.update({
|
||||
activeProduct
|
||||
}))
|
||||
}
|
||||
}, [activeProduct]);
|
||||
|
||||
function onPaymentError(error?: string | undefined): void {
|
||||
if (error === "Product not found") {
|
||||
return navigate(routes.client.compatibilityV2TrialChoice());
|
||||
}
|
||||
}
|
||||
|
||||
function onPaymentSuccess(): void {
|
||||
setTimeout(() => {
|
||||
navigate(routes.client.home());
|
||||
}, 1500);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
{!!activeProductFromStore && session?.["aura.test.payment"]?.length ?
|
||||
<PaymentPage
|
||||
placementKey={EPlacementKeys["aura.placement.payment"]}
|
||||
onError={onPaymentError}
|
||||
onSuccess={onPaymentSuccess}
|
||||
isBackButtonVisible={false}
|
||||
isAnonymous={true}
|
||||
sessionId={session?.["aura.test.payment"]}
|
||||
/>
|
||||
:
|
||||
<Loader color={LoaderColor.Black} />
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnonymousPaymentPage
|
||||
@ -0,0 +1,7 @@
|
||||
.container {
|
||||
min-width: 100%;
|
||||
min-height: 100dvh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
@ -30,6 +30,7 @@ import routes, {
|
||||
palmistryV2Prefix,
|
||||
emailMarketingV1Prefix,
|
||||
compatibilityV2Prefix,
|
||||
anonymousPrefix,
|
||||
} from "@/routes";
|
||||
import BirthdayPage from "../BirthdayPage";
|
||||
import BirthtimePage from "../BirthtimePage";
|
||||
@ -137,6 +138,7 @@ import PalmistryV2Routes from "@/routerComponents/Palmistry/v2";
|
||||
import MarketingLandingV1Routes from "@/routerComponents/MarketingLanding/v1";
|
||||
import { useScrollToTop } from "@/hooks/useScrollToTop";
|
||||
import CompatibilityV2Routes from "@/routerComponents/Compatibility/v2";
|
||||
import AnonymousRoutes from "@/routerComponents/Anonymous";
|
||||
|
||||
const isProduction = import.meta.env.MODE === "production";
|
||||
|
||||
@ -168,6 +170,7 @@ function App(): JSX.Element {
|
||||
routes.client.palmistryV1Welcome(),
|
||||
routes.client.compatibilityV2Welcome(),
|
||||
routes.client.palmistryWelcome(),
|
||||
routes.client.anonymousPayment(),
|
||||
];
|
||||
const isPageAvailable = availableUrls.reduce(
|
||||
(acc, url) => !!location.pathname.includes(url) || acc,
|
||||
@ -280,6 +283,10 @@ function App(): JSX.Element {
|
||||
<CookieYesController isDelete={subscriptionStatus === "subscribed"} />
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path={`${anonymousPrefix}/*`}
|
||||
element={<AnonymousRoutes />}
|
||||
/>
|
||||
<Route
|
||||
path={`${palmistryV1Prefix}/*`}
|
||||
element={<PalmistryV1Routes />}
|
||||
|
||||
@ -9,7 +9,7 @@ import { useTranslations } from "@/hooks/translations";
|
||||
import { ELocalesPlacement } from "@/locales";
|
||||
import { useCallback, useEffect, useState } from "react";
|
||||
import { sleep } from "@/services/date";
|
||||
import metricService from "@/services/metric/metricService";
|
||||
import metricService, { useMetricABFlags } from "@/services/metric/metricService";
|
||||
import { genders } from "@/components/pages/ABDesign/v1/data/genders";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import routes from "@/routes";
|
||||
@ -19,6 +19,7 @@ import { useSession } from "@/hooks/session/useSession";
|
||||
import { EGender, ESourceAuthorization } from "@/api/resources/User";
|
||||
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
|
||||
import Answer from "../../components/Answer";
|
||||
import Loader, { LoaderColor } from "@/components/Loader";
|
||||
|
||||
function GenderPage() {
|
||||
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
|
||||
@ -36,6 +37,8 @@ function GenderPage() {
|
||||
preloadKey: ELottieKeys.handSymbols,
|
||||
});
|
||||
|
||||
const { flags, ready } = useMetricABFlags();
|
||||
const pageType = flags?.genderPageType?.[0];
|
||||
const localGenders = genders.map((gender) => ({
|
||||
id: gender.id,
|
||||
title: translate(gender.id, undefined, ELocalesPlacement.V1),
|
||||
@ -81,34 +84,121 @@ function GenderPage() {
|
||||
}
|
||||
}, [gender, handleNext, isSelected, privacyPolicyChecked]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className={styles.title}>
|
||||
{translate("/gender.title")}
|
||||
</Title>
|
||||
<p className={styles.description}>{translate("/gender.description", {
|
||||
br: <br />,
|
||||
})}</p>
|
||||
{/* <ChooseGender onSelectGender={selectGender} /> */}
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
|
||||
<div className={styles["genders-container"]}>
|
||||
{localGenders.map((_gender, index) => (
|
||||
<Answer
|
||||
key={index}
|
||||
answer={_gender}
|
||||
isSelected={gender === _gender.id}
|
||||
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
|
||||
{/* {gender && !privacyPolicyChecked && (
|
||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
|
||||
</Toast>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
if (!ready) return <Loader color={LoaderColor.Black} />;
|
||||
|
||||
switch (pageType) {
|
||||
case "v1":
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className={styles.title}>
|
||||
{translate("/gender.v1.title", {
|
||||
br: <br />,
|
||||
})}
|
||||
</Title>
|
||||
<p className={styles.subtitle}>{translate("/gender.v1.subtitle", {
|
||||
br: <br />,
|
||||
})}</p>
|
||||
<ul className={styles.points}>
|
||||
{Array.from({ length: 4 }).map((_, index) => (
|
||||
<li key={index}>
|
||||
{translate(`/gender.v1.points.point${index + 1}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
{/* <ChooseGender onSelectGender={selectGender} /> */}
|
||||
<div className={styles["genders-container"]}>
|
||||
{localGenders.map((_gender, index) => (
|
||||
<Answer
|
||||
key={index}
|
||||
answer={_gender}
|
||||
isSelected={gender === _gender.id}
|
||||
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
|
||||
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
|
||||
{/* {gender && !privacyPolicyChecked && (
|
||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
|
||||
</Toast>
|
||||
)} */}
|
||||
</>
|
||||
)
|
||||
case "v2":
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className={styles.title}>
|
||||
{translate("/gender.v2.title", {
|
||||
br: <br />,
|
||||
})}
|
||||
</Title>
|
||||
<ul className={styles.points}>
|
||||
{Array.from({ length: 5 }).map((_, index) => (
|
||||
<li key={index}>
|
||||
{translate(`/gender.v2.points.point${index + 1}`)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p
|
||||
className={styles.subtitle}
|
||||
style={{
|
||||
marginTop: "28px",
|
||||
}}
|
||||
>{translate("/gender.v2.subtitle", {
|
||||
br: <br />,
|
||||
})}</p>
|
||||
{/* <ChooseGender onSelectGender={selectGender} /> */}
|
||||
<div className={styles["genders-container"]}>
|
||||
{localGenders.map((_gender, index) => (
|
||||
<Answer
|
||||
key={index}
|
||||
answer={_gender}
|
||||
isSelected={gender === _gender.id}
|
||||
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
|
||||
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
|
||||
{/* {gender && !privacyPolicyChecked && (
|
||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
|
||||
</Toast>
|
||||
)} */}
|
||||
</>
|
||||
)
|
||||
default:
|
||||
return (
|
||||
<>
|
||||
<Title variant="h2" className={styles.title}>
|
||||
{translate("/gender.title")}
|
||||
</Title>
|
||||
<p className={styles.description}>{translate("/gender.description", {
|
||||
br: <br />,
|
||||
})}</p>
|
||||
{/* <ChooseGender onSelectGender={selectGender} /> */}
|
||||
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
|
||||
<div className={styles["genders-container"]}>
|
||||
{localGenders.map((_gender, index) => (
|
||||
<Answer
|
||||
key={index}
|
||||
answer={_gender}
|
||||
isSelected={gender === _gender.id}
|
||||
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
|
||||
{/* {gender && !privacyPolicyChecked && (
|
||||
<Toast classNameContainer={styles["toast-container"]} variant="error">
|
||||
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
|
||||
</Toast>
|
||||
)} */}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default GenderPage;
|
||||
|
||||
@ -37,3 +37,29 @@
|
||||
flex-direction: column-reverse;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
font-size: 23px;
|
||||
line-height: 125%;
|
||||
font-weight: 300;
|
||||
color: #2C2C2C;
|
||||
}
|
||||
|
||||
.points {
|
||||
margin-top: 22px;
|
||||
color: #2C2C2C;
|
||||
list-style-type: disc;
|
||||
|
||||
li {
|
||||
font-size: 20px;
|
||||
line-height: 125%;
|
||||
font-weight: 300;
|
||||
margin-left: 28px;
|
||||
|
||||
&::marker {
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,6 +14,8 @@ interface ICheckoutFormProps {
|
||||
isHide?: boolean;
|
||||
placementKey: EPlacementKeys;
|
||||
activeProduct: IPaywallProduct;
|
||||
isAnonymous?: boolean;
|
||||
sessionId?: string;
|
||||
onSuccess?: () => void;
|
||||
onError?: (error?: string) => void;
|
||||
onModalClosed?: () => void;
|
||||
@ -22,6 +24,8 @@ interface ICheckoutFormProps {
|
||||
export default function CheckoutForm({
|
||||
placementKey,
|
||||
activeProduct,
|
||||
isAnonymous = false,
|
||||
sessionId = "",
|
||||
onError,
|
||||
onSuccess,
|
||||
onModalClosed,
|
||||
@ -39,7 +43,9 @@ export default function CheckoutForm({
|
||||
} = usePayment({
|
||||
placementKey,
|
||||
activeProduct,
|
||||
paymentFormType: "inline"
|
||||
paymentFormType: "inline",
|
||||
isAnonymous,
|
||||
sessionId
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -19,6 +19,9 @@ interface IPaymentPageProps {
|
||||
isSinglePayment?: boolean;
|
||||
placementKey: EPlacementKeys;
|
||||
className?: string;
|
||||
isBackButtonVisible?: boolean;
|
||||
isAnonymous?: boolean;
|
||||
sessionId?: string;
|
||||
onError?: (error?: string) => void;
|
||||
onSuccess?: () => void;
|
||||
onBack?: () => void;
|
||||
@ -36,6 +39,9 @@ function PaymentPage({
|
||||
isSinglePayment = false,
|
||||
placementKey,
|
||||
className = "",
|
||||
isBackButtonVisible = true,
|
||||
isAnonymous = false,
|
||||
sessionId = "",
|
||||
onError,
|
||||
onSuccess,
|
||||
onBack,
|
||||
@ -110,7 +116,7 @@ function PaymentPage({
|
||||
<Header
|
||||
className={styles.header}
|
||||
classNameTitle={styles["header-title"]}
|
||||
isBackButtonVisible={true}
|
||||
isBackButtonVisible={isBackButtonVisible}
|
||||
onBack={handleBack}
|
||||
/>
|
||||
{isLoading && (
|
||||
@ -157,6 +163,8 @@ function PaymentPage({
|
||||
{!!activeProduct && <CheckoutForm
|
||||
placementKey={placementKey}
|
||||
activeProduct={activeProduct}
|
||||
isAnonymous={isAnonymous}
|
||||
sessionId={sessionId}
|
||||
onError={onPaymentError}
|
||||
onSuccess={onPaymentSuccess}
|
||||
onModalClosed={onModalClosed}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { ErrorPayload, useApi } from "@/api";
|
||||
import { EGender, ESourceAuthorization, ICreateAuthorizePayload } from "@/api/resources/User";
|
||||
import { EGender, ESourceAuthorization, ICreateAuthorizePayload, ICreateAuthorizeResponse } from "@/api/resources/User";
|
||||
import { useAuth } from "@/auth";
|
||||
import { getClientTimezone } from "@/locales";
|
||||
import { getDateAsString } from "@/services/date";
|
||||
@ -202,6 +202,45 @@ export const useAuthentication = () => {
|
||||
}
|
||||
}, [api, dispatch, getAuthorizationPayload, signUp])
|
||||
|
||||
const anonymousAuthorization = useCallback(async ({
|
||||
token,
|
||||
userId: userIdFromApi = "",
|
||||
generatingVideo = false,
|
||||
videoId = "",
|
||||
authCode = ""
|
||||
}: ICreateAuthorizeResponse) => {
|
||||
try {
|
||||
setIsLoading(true);
|
||||
setError(null)
|
||||
const { user } = await api.getUser({ token });
|
||||
const { user: userMe } = await api.getMe({ token });
|
||||
const userId = userIdFromApi || userMe?._id;
|
||||
if (userId?.length) {
|
||||
dispatch(actions.userId.update({ userId }));
|
||||
metricService.userParams({
|
||||
hasPersonalVideo: generatingVideo || false,
|
||||
email: user?.email,
|
||||
UserID: userId
|
||||
})
|
||||
metricService.setUserID(userId);
|
||||
}
|
||||
signUp(token, user, userMe);
|
||||
setToken(token);
|
||||
if (authCode?.length) {
|
||||
dispatch(actions.userConfig.setAuthCode(authCode));
|
||||
}
|
||||
dispatch(actions.personalVideo.updateStatus({ generatingVideo: generatingVideo || false, videoId: videoId || "" }));
|
||||
if (generatingVideo) {
|
||||
metricService.reachGoal(EGoals.ROSE_VIDEO_CREATION_START, [EMetrics.YANDEX, EMetrics.KLAVIYO]);
|
||||
}
|
||||
dispatch(actions.status.update("registred"));
|
||||
} catch (error) {
|
||||
setError((error as Error).message);
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, [api, dispatch, getAuthorizationPayload, signUp])
|
||||
|
||||
return useMemo(
|
||||
() => ({
|
||||
isLoading,
|
||||
@ -210,6 +249,7 @@ export const useAuthentication = () => {
|
||||
user,
|
||||
authorization,
|
||||
authorizationWithPassword,
|
||||
anonymousAuthorization
|
||||
}),
|
||||
[
|
||||
isLoading,
|
||||
@ -218,6 +258,7 @@ export const useAuthentication = () => {
|
||||
user,
|
||||
authorization,
|
||||
authorizationWithPassword,
|
||||
anonymousAuthorization
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { useApi } from "@/api";
|
||||
import { ResponsePost } from "@/api/resources/Payment";
|
||||
import { ResponsePost, ResponsePostAnonymousSuccess } from "@/api/resources/Payment";
|
||||
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
|
||||
import { useAuthentication } from "@/hooks/authentication/use-authentication";
|
||||
import useElementRemovalObserver from "@/hooks/DOM/useElementRemovalObserver";
|
||||
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||
import { selectors } from "@/store";
|
||||
@ -14,6 +15,8 @@ interface IUsePaymentProps {
|
||||
cardNumberRef?: React.RefObject<HTMLDivElement>;
|
||||
cardExpiryRef?: React.RefObject<HTMLDivElement>;
|
||||
cardCvvRef?: React.RefObject<HTMLDivElement>;
|
||||
isAnonymous?: boolean;
|
||||
sessionId?: string;
|
||||
}
|
||||
|
||||
interface IFieldValidation {
|
||||
@ -31,11 +34,13 @@ export const usePayment = ({
|
||||
placementKey,
|
||||
activeProduct,
|
||||
paymentFormType = "lightbox",
|
||||
isAnonymous = false,
|
||||
sessionId = ""
|
||||
}: IUsePaymentProps) => {
|
||||
const api = useApi();
|
||||
const token = useSelector(selectors.selectToken);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
const [paymentResponse, setPaymentResponse] = useState<ResponsePost>();
|
||||
const [paymentResponse, setPaymentResponse] = useState<ResponsePost | ResponsePostAnonymousSuccess>();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
|
||||
const formPrice = String((activeProduct?.trialPrice || 99) / 100);
|
||||
@ -47,6 +52,8 @@ export const usePayment = ({
|
||||
cvv: { isValid: false, message: '' }
|
||||
});
|
||||
|
||||
const { anonymousAuthorization } = useAuthentication();
|
||||
|
||||
const isFormValid = useMemo(() => {
|
||||
return Object.values(formValidation).every(field => field.isValid);
|
||||
}, [formValidation]);
|
||||
@ -77,7 +84,7 @@ export const usePayment = ({
|
||||
}, [products, activeProduct]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!activeProduct || !token) return;
|
||||
if ((!activeProduct || !token) && !isAnonymous) return;
|
||||
|
||||
const config: any = {
|
||||
variant: paymentFormType,
|
||||
@ -142,13 +149,32 @@ export const usePayment = ({
|
||||
const finishSubmit = async (response: any) => {
|
||||
try {
|
||||
setIsSubmitting(true);
|
||||
const res = await api.makePayment({
|
||||
token,
|
||||
productId: activeProduct?._id || "",
|
||||
placementId,
|
||||
paywallId,
|
||||
paymentToken: response.token
|
||||
});
|
||||
if (isAnonymous && !sessionId?.length) {
|
||||
setError("Session ID is required for anonymous payment");
|
||||
throw new Error("Session ID is required for anonymous payment");
|
||||
}
|
||||
const res = !isAnonymous
|
||||
?
|
||||
await api.makePayment({
|
||||
token,
|
||||
productId: activeProduct?._id || "",
|
||||
placementId,
|
||||
paywallId,
|
||||
paymentToken: response.token
|
||||
})
|
||||
:
|
||||
await api.makeAnonymousPayment({
|
||||
productId: activeProduct?._id || "",
|
||||
placementId,
|
||||
paywallId,
|
||||
paymentToken: response.token,
|
||||
sessionId: sessionId
|
||||
})
|
||||
|
||||
if (isAnonymous && "user" in res && res?.user) {
|
||||
await anonymousAuthorization(res.user);
|
||||
}
|
||||
|
||||
setPaymentResponse(res);
|
||||
if ("payment" in res) {
|
||||
setIsPaymentSuccess(res.payment.status === "paid");
|
||||
|
||||
@ -1146,4 +1146,47 @@ export const defaultPaywalls: { [key in EPlacementKeys]: IPaywall } = {
|
||||
}
|
||||
]
|
||||
},
|
||||
"aura.placement.payment": {
|
||||
"name": "Anonymous Payment",
|
||||
"_id": "67b204c9ad3faa9d2fb5baf5",
|
||||
"key": "aura.paywall.payment",
|
||||
"properties": [
|
||||
{
|
||||
"key": "text.0",
|
||||
"value": "We've helped millions of people to\nreveal the destiny of their love life\nand what the future holds for them\nand their families.",
|
||||
"_id": "664542bbfe0a8eb4ee0b4f27"
|
||||
},
|
||||
{
|
||||
"key": "text.1",
|
||||
"value": "It costs us $13.21 to compensate our AURA\nemployees for the trial, but please choose the\namount you are comfortable with.",
|
||||
"_id": "664542bbfe0a8eb4ee0b4f29"
|
||||
},
|
||||
{
|
||||
"key": "split.price.value",
|
||||
"value": "2",
|
||||
"_id": "67a92d4fed3fa0664266fe2d"
|
||||
}
|
||||
],
|
||||
"products": [
|
||||
{
|
||||
"_id": "65ff043dfc0fcfc4be550035",
|
||||
"key": "compatibility.pdf.trial.0",
|
||||
"productId": "prod_PnStTEBzrPLgvL",
|
||||
"name": "Сompatibility AURA | Trial $0.99",
|
||||
"priceId": "price_1PpFiwIlX4lgwUxruq9bpp0j",
|
||||
"type": "subscription",
|
||||
"description": "Description",
|
||||
"discountPrice": null,
|
||||
"discountPriceId": null,
|
||||
"isDiscount": false,
|
||||
"isFreeTrial": false,
|
||||
"isTrial": true,
|
||||
"price": 1900,
|
||||
"trialDuration": 7,
|
||||
"trialPrice": 100,
|
||||
"trialPriceId": "price_1PpFoNIlX4lgwUxrP4l0lbE5",
|
||||
"currency": "usd"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -15,6 +15,7 @@ export const useSession = () => {
|
||||
const feature = useSelector(selectors.selectFeature)
|
||||
const { checked, dateOfCheck } = useSelector(selectors.selectPrivacyPolicy);
|
||||
const utm = useSelector(selectors.selectUTM);
|
||||
|
||||
const timezone = getClientTimezone();
|
||||
|
||||
const createSession = useCallback(async (source: ESourceAuthorization): Promise<ResponseCreate> => {
|
||||
|
||||
@ -115,7 +115,7 @@ export const getTranslationsJSON = async (language: string): Promise<TTranslatio
|
||||
try {
|
||||
return isDev
|
||||
?
|
||||
await (await fetch(`/locales/${place}/${defaultLanguage}/female_${defaultLanguage}.json`)).json()
|
||||
await (await fetch(`/locales/${place}/${defaultLanguage}/male_${defaultLanguage}.json`)).json()
|
||||
:
|
||||
await api.getLocaleTranslations({
|
||||
funnel: place,
|
||||
@ -136,7 +136,7 @@ export const getTranslationsJSON = async (language: string): Promise<TTranslatio
|
||||
|
||||
const result = successfulResponses.reduce((merged, current, index) => ({
|
||||
...merged,
|
||||
[placements[index]]: isDev ? { female: current } : current
|
||||
[placements[index]]: isDev ? { male: current } : current
|
||||
// [placements[index]]: current
|
||||
}), {});
|
||||
|
||||
|
||||
21
src/routerComponents/Anonymous/index.tsx
Normal file
21
src/routerComponents/Anonymous/index.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import AnonymousPaymentPage from '@/components/Anonymous/pages/Payment';
|
||||
import routes, { anonymousPrefix } from '@/routes'
|
||||
import { Route, Routes } from 'react-router-dom'
|
||||
|
||||
const removePrefix = (path: string) => path.replace(anonymousPrefix, "");
|
||||
|
||||
function AnonymousRoutes() {
|
||||
|
||||
return (
|
||||
<Routes>
|
||||
<Route
|
||||
path={`${removePrefix(routes.client.anonymousPayment())}/*`}
|
||||
element={
|
||||
<AnonymousPaymentPage />
|
||||
}
|
||||
/>
|
||||
</Routes>
|
||||
)
|
||||
}
|
||||
|
||||
export default AnonymousRoutes
|
||||
@ -17,6 +17,7 @@ export const palmistryV1Prefix = [host, "v1", "palmistry"].join("/")
|
||||
export const palmistryV2Prefix = [host, "v2", "palmistry"].join("/")
|
||||
export const palmistryEmailMarketingV2Prefix = [palmistryV2Prefix, "email-marketing"].join("/")
|
||||
export const emailMarketingV1Prefix = [host, "v1", "email-marketing"].join("/")
|
||||
export const anonymousPrefix = [host, "anonymous"].join("/")
|
||||
|
||||
export const chatsPrefix = [host, "chats"].join("/")
|
||||
|
||||
@ -251,6 +252,8 @@ const routes = {
|
||||
emailMarketingV1SpecialOffer: () => [emailMarketingV1Prefix, "special-offer"].join("/"),
|
||||
emailMarketingV1SaveOff: () => [emailMarketingV1Prefix, "save-off"].join("/"),
|
||||
emailMarketingV1SecretDiscount: () => [emailMarketingV1Prefix, "secret-discount"].join("/"),
|
||||
// Anonymous
|
||||
anonymousPayment: () => [anonymousPrefix, "payment"].join("/"),
|
||||
emailMarketingV1PaymentModal: () => [emailMarketingV1Prefix, "payment-modal"].join("/"),
|
||||
emailMarketingV1SecretDiscountPaymentModal: () => [emailMarketingV1Prefix, "secret-discount-payment-modal"].join("/"),
|
||||
emailMarketingV1SkipTrial: () => [emailMarketingV1Prefix, "skip-trial"].join("/"),
|
||||
@ -444,6 +447,7 @@ const routes = {
|
||||
|
||||
// Payment
|
||||
makePayment: () => [dApiHost, dApiPrefix, "payment", "checkout"].join("/"),
|
||||
makeAnonymousPayment: () => [dApiHost, dApiPrefix, "payment", "anonymous", "checkout"].join("/"),
|
||||
getPaymentConfig: () => [dApiHost, dApiPrefix, "payment", "config"].join("/"),
|
||||
// check payment method exist
|
||||
getPaymentMethods: () => [dApiHost, dApiPrefix, "payment", "method"].join("/"),
|
||||
|
||||
@ -206,7 +206,8 @@ type TABFlags = {
|
||||
auraVideoTrial: "on";
|
||||
auraPalmistry: "on";
|
||||
esFlag: "hiCopy" | "standard";
|
||||
palmOnPayment: "graphical" | "real"
|
||||
palmOnPayment: "graphical" | "real";
|
||||
genderPageType: "v1" | "v2";
|
||||
}
|
||||
|
||||
export const useMetricABFlags = () => {
|
||||
|
||||
@ -31,6 +31,7 @@ const initialState: TPaywalls = {
|
||||
"aura.placement.palmistry.secret.discount": null,
|
||||
"aura.placement.compatibility.v2": null,
|
||||
"aura.placement.compatibility.v2.secret.discount": null,
|
||||
"aura.placement.payment": null,
|
||||
isMustUpdate: {
|
||||
"aura.placement.v1.mike": true,
|
||||
"aura.placement.main": true,
|
||||
@ -46,6 +47,7 @@ const initialState: TPaywalls = {
|
||||
"aura.placement.palmistry.secret.discount": true,
|
||||
"aura.placement.compatibility.v2": true,
|
||||
"aura.placement.compatibility.v2.secret.discount": true,
|
||||
"aura.placement.payment": true,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@ -15,6 +15,7 @@ const initialState: TSessions = {
|
||||
"aura.palmistry.new": "",
|
||||
"aura.chats": "",
|
||||
"aura.compatibility.v2": "",
|
||||
"aura.test.payment": ""
|
||||
}
|
||||
|
||||
const sessionSlice = createSlice({
|
||||
|
||||
Loading…
Reference in New Issue
Block a user