Merge branch 'develop' into 'main'

develop

See merge request witapp/aura-webapp!615
This commit is contained in:
Daniil Chemerkin 2025-02-18 22:16:18 +00:00
commit a5006f4899
22 changed files with 472 additions and 48 deletions

View File

@ -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?",

View File

@ -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

View File

@ -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: {

View File

@ -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 {

View File

@ -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 {

View 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

View File

@ -0,0 +1,7 @@
.container {
min-width: 100%;
min-height: 100dvh;
display: flex;
align-items: center;
justify-content: center;
}

View File

@ -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 />}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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(() => {

View File

@ -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}

View File

@ -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
]
);
}

View File

@ -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");

View File

@ -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"
}
]
}
}

View File

@ -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> => {

View File

@ -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
}), {});

View 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

View File

@ -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("/"),

View File

@ -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 = () => {

View File

@ -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,
},
}

View File

@ -15,6 +15,7 @@ const initialState: TSessions = {
"aura.palmistry.new": "",
"aura.chats": "",
"aura.compatibility.v2": "",
"aura.test.payment": ""
}
const sessionSlice = createSlice({