develop
This commit is contained in:
parent
1c4e6bf706
commit
10f08b4e52
@ -683,6 +683,12 @@
|
|||||||
"description": "By continuing you agree that if you don't cancel prior to the end of the <trialDuration>-days trial, you will automatically be charged <price> for the introductory period of 14 days thereafter the standard rate of <price> every 14 days until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms."
|
"description": "By continuing you agree that if you don't cancel prior to the end of the <trialDuration>-days trial, you will automatically be charged <price> for the introductory period of 14 days thereafter the standard rate of <price> every 14 days until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms."
|
||||||
},
|
},
|
||||||
"/trial-choice": {
|
"/trial-choice": {
|
||||||
|
"text_variant_1": "AURA is the only accurate app with reliable astrological predictions, verified by professionals and guaranteed to provide accurate astrological forecasts. <br><br> AURA has already helped millions of people find happiness and discover the whole truth about their relationships. <br><br> An astrological forecast that will completely change your life is almost ready! Before we provide it to you, we would like to offer you the opportunity to choose the amount you consider reasonable to try AURA for <trialDuration> days and which you think is fair for the changes that will happen to you: <br><br> - You will discover all the most intimate secrets that the stars have prepared for you and solve relationship issues within just one month; <br> - You will once and for all put the finishing touches on unresolved issues and forget about problems that have been haunting you for years (if not decades); <br> - You will save hundreds of dollars on fake and unprofessional astrological predictions and fortune tellers; <br> - You will receive not only a personal astrological forecast but also personalized daily horoscopes, learn who and how is draining your energy, and get other personalized readings. <br><br> <span>",
|
||||||
|
"text_variant_1_span": "A <trialDuration>-day trial period costs us $5, but please choose the amount that suits you best:",
|
||||||
|
"text_variant_2": "AURA is the only app you can trust for accurate astrological insights, crafted and verified by seasoned professionals, ensuring you receive predictions that are both reliable and transformative. <br><br> AURA has already transformed the lives of millions, bringing clarity, joy, and a deeper understanding of their relationships. <br><br> Your life-changing astrological forecast is almost ready! But before we reveal the secrets that will change your life, we want to give you the freedom to choose how much you feel is fair to try AURA for <trialDuration> days. This is your chance to decide what the transformation is worth to you: <br><br> - Uncover the deepest, most intimate secrets the stars have in store for you, and watch your relationship issues resolve in just one month; <br> - Finally, put an end to those lingering issues that have been troubling you for years, maybe even decades; <br> - Save hundreds of dollars by avoiding unreliable astrologers and fake fortune tellers; <br> - Receive not just a personal astrological forecast but also personalized daily horoscopes, learn who's draining your energy, and get exclusive, tailored readings. <br><br><span>",
|
||||||
|
"text_variant_2_span": "While a <trialDuration>-day trial costs us $5, we want you to choose the amount you believe is right for you:",
|
||||||
|
"text_variant_3": "Discover AURA—the only app that delivers truly accurate astrological forecasts, with predictions you can trust, all verified by top industry professionals. Your path to clarity and happiness starts here. <br><br> Millions have already found happiness and uncovered the truth about their relationships with AURA. Now, it’s your turn. <br><br> Your life-changing astrological forecast is almost ready! Before we share this powerful insight with you, we’re giving you the chance to set your own price to experience AURA for <trialDuration> days. You decide what feels right for the life-changing revelations you’ll receive: <br><br> - Reveal the deepest secrets the universe has in store for you and resolve your relationship dilemmas within a month; <br> - Finally close the chapter on long-standing issues that have plagued you for years, perhaps even decades; <br> - Avoid wasting hundreds of dollars on untrustworthy, fake astrologers; <br> - Gain access to your personal astrological forecast, receive daily personalized horoscopes, learn who’s been draining your energy, and benefit from other insightful readings. <br><br><span>",
|
||||||
|
"text_variant_3_span": "Our <trialDuration>-day trial typically costs us $5, but you get to choose the amount that feels right for you:",
|
||||||
"button": "Choose an amount that you think is reasonable."
|
"button": "Choose an amount that you think is reasonable."
|
||||||
},
|
},
|
||||||
"skip_trial": "Skip Trial",
|
"skip_trial": "Skip Trial",
|
||||||
|
|||||||
1
public/old-backend/auraweb.json
Normal file
1
public/old-backend/auraweb.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"data":{"subscription_popup":true,"locale":"en","alerts":{},"version":"5.0","apple_music_api":{"jwt":"eyJhbGciOiJFUzI1NiIsImtpZCI6IlRSUDZXTUtERFYifQ.eyJpc3MiOiJLVEFMQTg4WkNSIiwiaWF0IjoxNzM2OTUyODM3LCJleHAiOjE3MzcwODI0Mzd9.0XjbuMOZ_7EcejBb5WEQxWH0tAkd1VqbbFJnYDDaKgpoLBd2fM7nYYHEOugKnmfWaJJt-OguJpmKI6opG1ilPg"},"chargebee":{"site":"kefirapp-test","publishableKey":"test_VtWSamZEfP175nqGZhkD0uvoouHieElv"},"first_open_subscription_popup":true,"runs_before_subscription_popup":0,"stripe_public_key":"pk_live_51Ndqf4IlX4lgwUxrdyEW5BKH30OLEYemyVj3XFqi3RNx3K149o0jNQEswuIutBXNQ4CeqJuODh6OMT9I3r1fq3VT00ncnJjWov","smartlook_manage":false,"appirater_alerts":[],"active_iaps":[{"bundle_id":"auraweb.yearlymembership","active":true,"subscription_type":"yearly"},{"bundle_id":"auraweb.weeklymembership","active":true,"subscription_type":"trial"},{"bundle_id":"auraweb.weekmembership","active":true,"subscription_type":"trial"},{"bundle_id":"auraweb.monthlymembership","active":true,"subscription_type":"monthly"}]}}
|
||||||
1
public/old-backend/elements.json
Normal file
1
public/old-backend/elements.json
Normal file
@ -0,0 +1 @@
|
|||||||
|
{"data":{"variant":"a","groups":[]}}
|
||||||
1
public/old-backend/t.json
Normal file
1
public/old-backend/t.json
Normal file
File diff suppressed because one or more lines are too long
Binary file not shown.
|
Before Width: | Height: | Size: 40 KiB |
25
public/v1/email-marketing/messages.svg
Normal file
25
public/v1/email-marketing/messages.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 72 KiB |
BIN
public/v1/email-marketing/payments.png
Normal file
BIN
public/v1/email-marketing/payments.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 572 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 135 KiB |
706
public/v1/email-marketing/smartphone.svg
Normal file
706
public/v1/email-marketing/smartphone.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 4.5 MiB |
@ -2,15 +2,12 @@ import { createMethod } from './utils'
|
|||||||
import {
|
import {
|
||||||
User,
|
User,
|
||||||
Auras,
|
Auras,
|
||||||
Element,
|
|
||||||
Elements,
|
Elements,
|
||||||
AuthTokens,
|
AuthTokens,
|
||||||
Apps,
|
Apps,
|
||||||
Assets,
|
Assets,
|
||||||
AssetCategories,
|
AssetCategories,
|
||||||
DailyForecasts,
|
DailyForecasts,
|
||||||
SubscriptionItems,
|
|
||||||
SubscriptionCheckout,
|
|
||||||
SubscriptionStatus,
|
SubscriptionStatus,
|
||||||
AICompatCategories,
|
AICompatCategories,
|
||||||
AICompats,
|
AICompats,
|
||||||
@ -18,9 +15,6 @@ import {
|
|||||||
UserCallbacks,
|
UserCallbacks,
|
||||||
Translations,
|
Translations,
|
||||||
Zodiacs,
|
Zodiacs,
|
||||||
GoogleAuth,
|
|
||||||
SubscriptionPlans,
|
|
||||||
AppleAuth,
|
|
||||||
AIRequestsV2,
|
AIRequestsV2,
|
||||||
Assistants,
|
Assistants,
|
||||||
OpenAI,
|
OpenAI,
|
||||||
@ -41,12 +35,10 @@ import {
|
|||||||
} from './resources'
|
} from './resources'
|
||||||
|
|
||||||
const api = {
|
const api = {
|
||||||
auth: createMethod<AuthTokens.Payload, AuthTokens.Response>(AuthTokens.createRequest),
|
// auth: createMethod<AuthTokens.Payload, AuthTokens.Response>(AuthTokens.createRequest),
|
||||||
appleAuth: createMethod<AppleAuth.Payload, AppleAuth.Response>(AppleAuth.createRequest),
|
|
||||||
googleAuth: createMethod<GoogleAuth.Payload, GoogleAuth.Response>(GoogleAuth.createRequest),
|
|
||||||
getRealToken: createMethod<AuthTokens.PayloadGetRealToken, AuthTokens.ResponseGetRealToken>(AuthTokens.createGetRealTokenRequest),
|
getRealToken: createMethod<AuthTokens.PayloadGetRealToken, AuthTokens.ResponseGetRealToken>(AuthTokens.createGetRealTokenRequest),
|
||||||
getAppConfig: createMethod<Apps.Payload, Apps.Response>(Apps.createRequest),
|
getAppConfig: createMethod<Apps.Payload, Apps.Response>(Apps.createRequest),
|
||||||
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),
|
||||||
updateUser: createMethod<User.PatchPayload, User.Response>(User.createPatchRequest),
|
updateUser: createMethod<User.PatchPayload, User.Response>(User.createPatchRequest),
|
||||||
@ -54,9 +46,8 @@ const api = {
|
|||||||
getAssetCategories: createMethod<AssetCategories.Payload, AssetCategories.Response>(AssetCategories.createRequest),
|
getAssetCategories: createMethod<AssetCategories.Payload, AssetCategories.Response>(AssetCategories.createRequest),
|
||||||
getDailyForecasts: createMethod<DailyForecasts.Payload, DailyForecasts.Response>(DailyForecasts.createRequest),
|
getDailyForecasts: createMethod<DailyForecasts.Payload, DailyForecasts.Response>(DailyForecasts.createRequest),
|
||||||
getAuras: createMethod<Auras.Payload, Auras.Response>(Auras.createRequest),
|
getAuras: createMethod<Auras.Payload, Auras.Response>(Auras.createRequest),
|
||||||
getSubscriptionItems: createMethod<SubscriptionItems.Payload, SubscriptionItems.Response>(SubscriptionItems.createRequest),
|
// getSubscriptionPlans: createMethod<SubscriptionPlans.Payload, SubscriptionPlans.Response>(SubscriptionPlans.createRequest),
|
||||||
getSubscriptionPlans: createMethod<SubscriptionPlans.Payload, SubscriptionPlans.Response>(SubscriptionPlans.createRequest),
|
// getSubscriptionCheckout: createMethod<SubscriptionCheckout.Payload, SubscriptionCheckout.Response>(SubscriptionCheckout.createRequest),
|
||||||
getSubscriptionCheckout: createMethod<SubscriptionCheckout.Payload, SubscriptionCheckout.Response>(SubscriptionCheckout.createRequest),
|
|
||||||
getSubscriptionStatus: createMethod<SubscriptionStatus.Payload, SubscriptionStatus.Response>(SubscriptionStatus.createRequest),
|
getSubscriptionStatus: createMethod<SubscriptionStatus.Payload, SubscriptionStatus.Response>(SubscriptionStatus.createRequest),
|
||||||
// new get subscription status
|
// new get subscription status
|
||||||
getSubscriptionStatusNew: createMethod<SubscriptionStatus.Payload, SubscriptionStatus.ResponseNew>(SubscriptionStatus.createRequestNew),
|
getSubscriptionStatusNew: createMethod<SubscriptionStatus.Payload, SubscriptionStatus.ResponseNew>(SubscriptionStatus.createRequestNew),
|
||||||
@ -101,6 +92,7 @@ const api = {
|
|||||||
// Session
|
// Session
|
||||||
createSession: createMethod<Session.PayloadCreate, Session.ResponseCreate>(Session.createRequest),
|
createSession: createMethod<Session.PayloadCreate, Session.ResponseCreate>(Session.createRequest),
|
||||||
updateSession: createMethod<Session.PayloadUpdate, Session.ResponseUpdate>(Session.updateRequest),
|
updateSession: createMethod<Session.PayloadUpdate, Session.ResponseUpdate>(Session.updateRequest),
|
||||||
|
getLocaleTranslations: createMethod<Session.PayloadGetLocale, Session.ResponseGetLocale>(Session.getLocaleRequest),
|
||||||
// Chats
|
// Chats
|
||||||
getChatsCategories: createMethod<null, ChatsCategories.ResponseGet>(ChatsCategories.getRequest),
|
getChatsCategories: createMethod<null, ChatsCategories.ResponseGet>(ChatsCategories.getRequest),
|
||||||
getChatMessages: createMethod<ChatMessages.Payload, ChatMessages.ResponseGet>(ChatMessages.getRequest),
|
getChatMessages: createMethod<ChatMessages.Payload, ChatMessages.ResponseGet>(ChatMessages.getRequest),
|
||||||
|
|||||||
@ -1,13 +0,0 @@
|
|||||||
import routes from "@/routes";
|
|
||||||
|
|
||||||
export interface Payload {
|
|
||||||
origin: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Response = unknown;
|
|
||||||
|
|
||||||
export const createRequest = ({ origin }: Payload): Request => {
|
|
||||||
|
|
||||||
const url = new URL(routes.server.appleAuth(origin));
|
|
||||||
return new Request(url, { method: "POST" });
|
|
||||||
};
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
import routes from "@/routes";
|
import routes from "@/routes";
|
||||||
import { AuthToken } from "../types";
|
import { AuthToken } from "../types";
|
||||||
import { User } from "./User";
|
import { User } from "./User";
|
||||||
import { getAuthHeaders, getBaseHeaders } from "../utils";
|
import { getAuthHeaders } from "../utils";
|
||||||
|
|
||||||
export interface PayloadRegisterByEmail {
|
export interface PayloadRegisterByEmail {
|
||||||
email: string;
|
email: string;
|
||||||
@ -13,7 +13,7 @@ export interface PayloadAuthWithJWT {
|
|||||||
jwt: string;
|
jwt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Payload = PayloadRegisterByEmail | PayloadAuthWithJWT;
|
// export type Payload = PayloadRegisterByEmail | PayloadAuthWithJWT;
|
||||||
|
|
||||||
export interface Response {
|
export interface Response {
|
||||||
auth: {
|
auth: {
|
||||||
@ -36,11 +36,11 @@ export interface JwtPayload {
|
|||||||
iss: string;
|
iss: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const createRequest = (payload: Payload): Request => {
|
// export const createRequest = (payload: Payload): Request => {
|
||||||
const url = new URL(routes.server.token());
|
// const url = new URL(routes.server.token());
|
||||||
const body = JSON.stringify({ auth: { ...payload } });
|
// const body = JSON.stringify({ auth: { ...payload } });
|
||||||
return new Request(url, { method: "POST", headers: getBaseHeaders(), body });
|
// return new Request(url, { method: "POST", headers: getBaseHeaders(), body });
|
||||||
};
|
// };
|
||||||
|
|
||||||
export interface PayloadGetRealToken {
|
export interface PayloadGetRealToken {
|
||||||
token: string;
|
token: string;
|
||||||
|
|||||||
@ -1,31 +0,0 @@
|
|||||||
import routes from '@/routes'
|
|
||||||
import { getBaseHeaders } from '../utils'
|
|
||||||
|
|
||||||
export interface Payload {
|
|
||||||
locale: string
|
|
||||||
type: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
data: {
|
|
||||||
variant: string
|
|
||||||
element: Element
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Element {
|
|
||||||
type: string
|
|
||||||
href: string
|
|
||||||
title: string
|
|
||||||
url_slug: string
|
|
||||||
body: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequest = ({ locale, type }: Payload): Request => {
|
|
||||||
const url = new URL(routes.server.element(type))
|
|
||||||
const query = new URLSearchParams({ locale })
|
|
||||||
|
|
||||||
url.search = query.toString()
|
|
||||||
|
|
||||||
return new Request(url, { method: 'GET', headers: getBaseHeaders() })
|
|
||||||
}
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
export interface Payload {
|
|
||||||
requestUrl: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
access_token: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequest = ({ requestUrl }: Payload): Request => {
|
|
||||||
const url = new URL(requestUrl);
|
|
||||||
return new Request(url, {
|
|
||||||
method: "GET",
|
|
||||||
});
|
|
||||||
};
|
|
||||||
@ -17,7 +17,9 @@ export enum EPlacementKeys {
|
|||||||
"aura.placement.secret.discount" = "aura.placement.secret.discount",
|
"aura.placement.secret.discount" = "aura.placement.secret.discount",
|
||||||
"aura.placement.palmistry.main" = "aura.placement.palmistry.main",
|
"aura.placement.palmistry.main" = "aura.placement.palmistry.main",
|
||||||
"aura.placement.palmistry.redesign" = "aura.placement.palmistry.redesign",
|
"aura.placement.palmistry.redesign" = "aura.placement.palmistry.redesign",
|
||||||
"aura.placement.chat" = "aura.placement.chat"
|
"aura.placement.chat" = "aura.placement.chat",
|
||||||
|
"aura.placement.email.palmistry" = "aura.placement.email.palmistry",
|
||||||
|
"aura.placement.email.palmistry.discount" = "aura.placement.email.palmistry.discount"
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResponseGetSuccess {
|
export interface ResponseGetSuccess {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import routes from "@/routes";
|
|||||||
import { getBaseHeaders } from "../utils";
|
import { getBaseHeaders } from "../utils";
|
||||||
import { IUTM } from "@/store/utm";
|
import { IUTM } from "@/store/utm";
|
||||||
import { ICreateAuthorizeUser } from "./User";
|
import { ICreateAuthorizeUser } from "./User";
|
||||||
|
import { ELocalesPlacement } from "@/locales";
|
||||||
|
|
||||||
export interface PayloadCreate {
|
export interface PayloadCreate {
|
||||||
feature: string, // Type: string
|
feature: string, // Type: string
|
||||||
@ -78,6 +79,15 @@ export interface ResponseUpdate {
|
|||||||
message: "Session updated" | string
|
message: "Session updated" | string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ResponseGetLocale {
|
||||||
|
male: Record<string, string>
|
||||||
|
female: Record<string, string>
|
||||||
|
fallback: {
|
||||||
|
male: Record<string, string>,
|
||||||
|
female: Record<string, string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const createRequest = (data: PayloadCreate) => {
|
export const createRequest = (data: PayloadCreate) => {
|
||||||
const url = new URL(routes.server.createSession());
|
const url = new URL(routes.server.createSession());
|
||||||
const body = JSON.stringify(data);
|
const body = JSON.stringify(data);
|
||||||
@ -97,3 +107,18 @@ export const updateRequest = ({ data, sessionId }: PayloadUpdate) => {
|
|||||||
headers: getBaseHeaders()
|
headers: getBaseHeaders()
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface PayloadGetLocale {
|
||||||
|
funnel: ELocalesPlacement,
|
||||||
|
locale: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLocaleRequest = (data: PayloadGetLocale) => {
|
||||||
|
const url = new URL(routes.server.getLocale());
|
||||||
|
const body = JSON.stringify(data);
|
||||||
|
return new Request(url, {
|
||||||
|
method: "POST",
|
||||||
|
body,
|
||||||
|
headers: getBaseHeaders()
|
||||||
|
});
|
||||||
|
};
|
||||||
@ -1,43 +0,0 @@
|
|||||||
import routes from "@/routes";
|
|
||||||
|
|
||||||
export interface Payload {
|
|
||||||
locale: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
sub_plans: ISubscriptionPlan[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ISubscriptionPlan {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
desc: string;
|
|
||||||
provider: "stripe" | "paypal";
|
|
||||||
interval: "week" | "month" | "year";
|
|
||||||
price_cents: number;
|
|
||||||
trial: ITrial | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ITrial {
|
|
||||||
is_paid: boolean;
|
|
||||||
is_free: boolean;
|
|
||||||
days: number;
|
|
||||||
price_cents: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AssetMetadata {
|
|
||||||
size: number;
|
|
||||||
width: number;
|
|
||||||
height: number;
|
|
||||||
filename: string;
|
|
||||||
mime_type: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequest = ({ locale }: Payload): Request => {
|
|
||||||
const url = new URL(routes.server.subscriptionPlans());
|
|
||||||
const query = new URLSearchParams({ locale });
|
|
||||||
|
|
||||||
url.search = query.toString();
|
|
||||||
|
|
||||||
return new Request(url, { method: "GET" });
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import routes from "@/routes"
|
|
||||||
import { AuthPayload } from "../types"
|
|
||||||
import { getAuthHeaders } from "../utils"
|
|
||||||
|
|
||||||
export interface Payload extends AuthPayload {
|
|
||||||
embed?: boolean
|
|
||||||
locale: string
|
|
||||||
itemPriceId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
hosted_page: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface HostedPage {
|
|
||||||
id: string
|
|
||||||
url: string
|
|
||||||
embed: boolean
|
|
||||||
type: string
|
|
||||||
object: string
|
|
||||||
state: string
|
|
||||||
resource_version: number
|
|
||||||
created_at: number
|
|
||||||
updated_at: number
|
|
||||||
expires_at: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequest = ({ locale, token, itemPriceId, embed = false }: Payload): Request => {
|
|
||||||
const url = new URL(routes.server.subscriptionCheckout())
|
|
||||||
const query = new URLSearchParams({ locale, item_price_id: itemPriceId, embed: embed.toString() })
|
|
||||||
|
|
||||||
url.search = query.toString()
|
|
||||||
|
|
||||||
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
|
||||||
}
|
|
||||||
@ -1,40 +0,0 @@
|
|||||||
import routes from "@/routes"
|
|
||||||
import { AuthPayload } from "../types"
|
|
||||||
import { getAuthHeaders } from "../utils"
|
|
||||||
|
|
||||||
export interface Payload extends AuthPayload {
|
|
||||||
locale: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
item_prices: ItemPrice[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ItemPrice {
|
|
||||||
currency_code: string
|
|
||||||
external_name: string
|
|
||||||
free_quantity: number
|
|
||||||
id: string
|
|
||||||
is_taxable: boolean
|
|
||||||
item_id: string
|
|
||||||
item_type: string
|
|
||||||
name: string
|
|
||||||
object: string
|
|
||||||
period: number
|
|
||||||
period_unit: string
|
|
||||||
price: number
|
|
||||||
pricing_model: string
|
|
||||||
resource_version: number
|
|
||||||
status: string
|
|
||||||
created_at: number
|
|
||||||
updated_at: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export const createRequest = ({ locale, token }: Payload): Request => {
|
|
||||||
const url = new URL(routes.server.subscriptionItems())
|
|
||||||
const query = new URLSearchParams({ locale })
|
|
||||||
|
|
||||||
url.search = query.toString()
|
|
||||||
|
|
||||||
return new Request(url, { method: 'GET', headers: getAuthHeaders(token) })
|
|
||||||
}
|
|
||||||
@ -1,104 +0,0 @@
|
|||||||
import routes from "@/routes";
|
|
||||||
import { AuthPayload } from "../types";
|
|
||||||
import { getAuthHeaders } from "../utils";
|
|
||||||
|
|
||||||
export interface GetPayload extends AuthPayload {
|
|
||||||
id: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AppleReceiptPayload extends AuthPayload {
|
|
||||||
receiptData: string;
|
|
||||||
autorenewable?: boolean;
|
|
||||||
sandbox?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface StripeReceiptPayload extends AuthPayload {
|
|
||||||
way: "stripe";
|
|
||||||
subscription_receipt: {
|
|
||||||
sub_plan_id: string;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export type Payload =
|
|
||||||
| AppleReceiptPayload
|
|
||||||
| StripeReceiptPayload
|
|
||||||
|
|
||||||
export interface Response {
|
|
||||||
subscription_receipt: SubscriptionReceipt;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SubscriptionReceipt {
|
|
||||||
id: string;
|
|
||||||
user_id: number;
|
|
||||||
status: number;
|
|
||||||
expires_at: null | string;
|
|
||||||
requested_at: string;
|
|
||||||
created_at: string;
|
|
||||||
data: {
|
|
||||||
input: {
|
|
||||||
subscription_items: [
|
|
||||||
{
|
|
||||||
item_price_id: string;
|
|
||||||
}
|
|
||||||
];
|
|
||||||
payment_intent: {
|
|
||||||
gw_token: string;
|
|
||||||
gateway_account_id: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
client_secret: string;
|
|
||||||
app_bundle_id: string;
|
|
||||||
autorenewable: boolean;
|
|
||||||
error: string;
|
|
||||||
stripe_status?: string;
|
|
||||||
checkout_url?: string;
|
|
||||||
checkout_session?: unknown;
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function createRequest({
|
|
||||||
token,
|
|
||||||
receiptData,
|
|
||||||
autorenewable,
|
|
||||||
sandbox,
|
|
||||||
}: AppleReceiptPayload): Request;
|
|
||||||
function createRequest({ token }: StripeReceiptPayload): Request;
|
|
||||||
function createRequest(payload: Payload): Request;
|
|
||||||
function createRequest(payload: Payload): Request {
|
|
||||||
const url = new URL(routes.server.subscriptionReceipts());
|
|
||||||
const data = getDataPayload(payload);
|
|
||||||
const body = JSON.stringify(data);
|
|
||||||
return new Request(url, {
|
|
||||||
method: "POST",
|
|
||||||
headers: getAuthHeaders(payload.token),
|
|
||||||
body,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function getDataPayload(payload: Payload) {
|
|
||||||
if ("receiptData" in payload) {
|
|
||||||
return {
|
|
||||||
way: "apple",
|
|
||||||
subscription_receipt: {
|
|
||||||
receipt_data: payload.receiptData,
|
|
||||||
autorenewable: payload.autorenewable,
|
|
||||||
sandbox: payload.sandbox,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
if ("way" in payload && payload.way === "stripe") {
|
|
||||||
return {
|
|
||||||
way: "stripe",
|
|
||||||
subscription_receipt: {
|
|
||||||
sub_plan_id: payload.subscription_receipt.sub_plan_id,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createGetRequest({ id, token }: GetPayload): Request {
|
|
||||||
const url = new URL(routes.server.subscriptionReceipt(id));
|
|
||||||
return new Request(url, { method: "GET", headers: getAuthHeaders(token) });
|
|
||||||
}
|
|
||||||
|
|
||||||
export { createRequest, createGetRequest };
|
|
||||||
@ -4,11 +4,8 @@ export * as Apps from "./Apps";
|
|||||||
export * as User from "./User";
|
export * as User from "./User";
|
||||||
export * as DailyForecasts from "./UserDailyForecasts";
|
export * as DailyForecasts from "./UserDailyForecasts";
|
||||||
export * as Auras from "./Auras";
|
export * as Auras from "./Auras";
|
||||||
export * as Element from "./Element";
|
|
||||||
export * as Elements from "./Elements";
|
export * as Elements from "./Elements";
|
||||||
export * as AuthTokens from "./AuthTokens";
|
export * as AuthTokens from "./AuthTokens";
|
||||||
export * as SubscriptionItems from "./UserSubscriptionItemPrices";
|
|
||||||
export * as SubscriptionCheckout from "./UserSubscriptionCheckout";
|
|
||||||
export * as SubscriptionStatus from "./UserSubscriptionStatus";
|
export * as SubscriptionStatus from "./UserSubscriptionStatus";
|
||||||
export * as AICompatCategories from "./AICompatCategories";
|
export * as AICompatCategories from "./AICompatCategories";
|
||||||
export * as AICompats from "./AICompats";
|
export * as AICompats from "./AICompats";
|
||||||
@ -16,9 +13,6 @@ export * as AIRequests from "./AIRequests";
|
|||||||
export * as UserCallbacks from "./UserCallbacks";
|
export * as UserCallbacks from "./UserCallbacks";
|
||||||
export * as Translations from "./Translations";
|
export * as Translations from "./Translations";
|
||||||
export * as Zodiacs from "./Zodiacs";
|
export * as Zodiacs from "./Zodiacs";
|
||||||
export * as GoogleAuth from "./GoogleAuth";
|
|
||||||
export * as SubscriptionPlans from "./SubscriptionPlans";
|
|
||||||
export * as AppleAuth from "./AppleAuth";
|
|
||||||
export * as AIRequestsV2 from "./AIRequestsV2";
|
export * as AIRequestsV2 from "./AIRequestsV2";
|
||||||
export * as Assistants from "./Assistants";
|
export * as Assistants from "./Assistants";
|
||||||
export * as OpenAI from "./OpenAI";
|
export * as OpenAI from "./OpenAI";
|
||||||
|
|||||||
@ -34,10 +34,8 @@ import BirthdayPage from "../BirthdayPage";
|
|||||||
import BirthtimePage from "../BirthtimePage";
|
import BirthtimePage from "../BirthtimePage";
|
||||||
import CreateProfilePage from "../CreateProfilePage";
|
import CreateProfilePage from "../CreateProfilePage";
|
||||||
import EmailEnterPage from "../EmailEnterPage";
|
import EmailEnterPage from "../EmailEnterPage";
|
||||||
import SubscriptionPage from "../SubscriptionPage";
|
|
||||||
import PaymentPage from "../PaymentPage";
|
import PaymentPage from "../PaymentPage";
|
||||||
import WallpaperPage from "../WallpaperPage";
|
import WallpaperPage from "../WallpaperPage";
|
||||||
import StaticPage from "../StaticPage";
|
|
||||||
import NotFoundPage from "../NotFoundPage";
|
import NotFoundPage from "../NotFoundPage";
|
||||||
import Header from "../Header";
|
import Header from "../Header";
|
||||||
import Navbar from "../Navbar";
|
import Navbar from "../Navbar";
|
||||||
@ -45,7 +43,6 @@ import Footer from "../Footer";
|
|||||||
import "./styles.css";
|
import "./styles.css";
|
||||||
import DidYouKnowPage from "../DidYouKnowPage";
|
import DidYouKnowPage from "../DidYouKnowPage";
|
||||||
import FreePeriodInfoPage from "../FreePeriodInfoPage";
|
import FreePeriodInfoPage from "../FreePeriodInfoPage";
|
||||||
import AttentionPage from "../AttentionPage";
|
|
||||||
import FeedbackPage from "../FeedbackPage";
|
import FeedbackPage from "../FeedbackPage";
|
||||||
import CompatibilityPage from "../Compatibility";
|
import CompatibilityPage from "../Compatibility";
|
||||||
import BreathPage from "../BreathPage";
|
import BreathPage from "../BreathPage";
|
||||||
@ -147,13 +144,11 @@ if (isProduction) {
|
|||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const [isSpecialOfferOpen, setIsSpecialOfferOpen] = useState<boolean>(false);
|
|
||||||
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
const [leoApng, setLeoApng] = useState<Error | APNG>(Error);
|
||||||
// const [
|
// const [
|
||||||
// padLockApng,
|
// padLockApng,
|
||||||
// setPadLockApng,
|
// setPadLockApng,
|
||||||
// ] = useState<Error | APNG>(Error);
|
// ] = useState<Error | APNG>(Error);
|
||||||
const navigate = useNavigate();
|
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { token, user, signUp, logout } = useAuth();
|
const { token, user, signUp, logout } = useAuth();
|
||||||
@ -228,11 +223,6 @@ function App(): JSX.Element {
|
|||||||
});
|
});
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const closeSpecialOfferAttention = () => {
|
|
||||||
setIsSpecialOfferOpen(false);
|
|
||||||
navigate(routes.client.auth());
|
|
||||||
};
|
|
||||||
|
|
||||||
const assetsData = useCallback(async () => {
|
const assetsData = useCallback(async () => {
|
||||||
const { assets } = await api.getAssets({
|
const { assets } = await api.getAssets({
|
||||||
category: String("au"),
|
category: String("au"),
|
||||||
@ -345,7 +335,7 @@ function App(): JSX.Element {
|
|||||||
element={<MikeV1Routes />}
|
element={<MikeV1Routes />}
|
||||||
/>
|
/>
|
||||||
<Route
|
<Route
|
||||||
element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}
|
element={<Layout />}
|
||||||
>
|
>
|
||||||
<Route path={routes.client.loadingPage()} element={<LoadingPage />} />
|
<Route path={routes.client.loadingPage()} element={<LoadingPage />} />
|
||||||
{/* Email - Pay - Email */}
|
{/* Email - Pay - Email */}
|
||||||
@ -852,15 +842,6 @@ function App(): JSX.Element {
|
|||||||
path={routes.client.freePeriodInfo()}
|
path={routes.client.freePeriodInfo()}
|
||||||
element={<FreePeriodInfoPage />}
|
element={<FreePeriodInfoPage />}
|
||||||
/>
|
/>
|
||||||
<Route
|
|
||||||
path={routes.client.attention()}
|
|
||||||
element={
|
|
||||||
<AttentionPage
|
|
||||||
isOpenModal={isSpecialOfferOpen}
|
|
||||||
onCloseSpecialOffer={closeSpecialOfferAttention}
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Route path={routes.client.feedback()} element={<FeedbackPage />} />
|
<Route path={routes.client.feedback()} element={<FeedbackPage />} />
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.birthtime()}
|
path={routes.client.birthtime()}
|
||||||
@ -882,20 +863,20 @@ function App(): JSX.Element {
|
|||||||
path={routes.client.authResult()}
|
path={routes.client.authResult()}
|
||||||
element={<AuthResultPage />}
|
element={<AuthResultPage />}
|
||||||
/>
|
/>
|
||||||
<Route path={routes.client.static()} element={<StaticPage />} />
|
{/* <Route path={routes.client.static()} element={<StaticPage />} /> */}
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.priceList()}
|
path={routes.client.priceList()}
|
||||||
element={<PriceListPage />}
|
element={<PriceListPage />}
|
||||||
/>
|
/>
|
||||||
</Route>
|
</Route>
|
||||||
<Route element={<AuthorizedUserOutlet />}>
|
{/* <Route element={<AuthorizedUserOutlet />}>
|
||||||
<Route
|
<Route
|
||||||
path={routes.client.subscription()}
|
path={routes.client.subscription()}
|
||||||
element={<SubscriptionPage />}
|
element={<SubscriptionPage />}
|
||||||
>
|
>
|
||||||
<Route path=":subPlan" element={<SubscriptionPage />} />
|
<Route path=":subPlan" element={<SubscriptionPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
</Route>
|
</Route> */}
|
||||||
<Route element={<PrivateOutlet />}>
|
<Route element={<PrivateOutlet />}>
|
||||||
<Route element={<AuthorizedUserOutlet />}>
|
<Route element={<AuthorizedUserOutlet />}>
|
||||||
<Route
|
<Route
|
||||||
@ -975,11 +956,7 @@ function App(): JSX.Element {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LayoutProps {
|
function Layout(): JSX.Element {
|
||||||
setIsSpecialOfferOpen: React.Dispatch<React.SetStateAction<boolean>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -988,7 +965,6 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
|
|||||||
const showHeader = hasNoHeader(location.pathname);
|
const showHeader = hasNoHeader(location.pathname);
|
||||||
const isRouteFullDataModal = hasFullDataModal(location.pathname);
|
const isRouteFullDataModal = hasFullDataModal(location.pathname);
|
||||||
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
const [isMenuOpen, setIsMenuOpen] = useState<boolean>(false);
|
||||||
const changeIsSpecialOfferOpen = () => setIsSpecialOfferOpen(true);
|
|
||||||
const homeConfig = useSelector(selectors.selectHome);
|
const homeConfig = useSelector(selectors.selectHome);
|
||||||
const showNavbarFooter = homeConfig.isShowNavbar;
|
const showNavbarFooter = homeConfig.isShowNavbar;
|
||||||
const mainRef = useRef<HTMLDivElement>(null);
|
const mainRef = useRef<HTMLDivElement>(null);
|
||||||
@ -1081,7 +1057,6 @@ function Layout({ setIsSpecialOfferOpen }: LayoutProps): JSX.Element {
|
|||||||
{showHeader ? (
|
{showHeader ? (
|
||||||
<Header
|
<Header
|
||||||
openMenu={() => setIsMenuOpen(true)}
|
openMenu={() => setIsMenuOpen(true)}
|
||||||
clickCross={changeIsSpecialOfferOpen}
|
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
{isRouteFullDataModal && (
|
{isRouteFullDataModal && (
|
||||||
|
|||||||
@ -1,40 +0,0 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useTranslations } from "@/hooks/translations";
|
|
||||||
import Title from "../Title";
|
|
||||||
import routes from "@/routes";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
import SpecialWelcomeOffer from "../SpecialWelcomeOffer";
|
|
||||||
import MainButton from "../MainButton";
|
|
||||||
|
|
||||||
interface AttentionPageProps {
|
|
||||||
isOpenModal: boolean;
|
|
||||||
onCloseSpecialOffer?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AttentionPage({
|
|
||||||
isOpenModal,
|
|
||||||
onCloseSpecialOffer,
|
|
||||||
}: AttentionPageProps): JSX.Element {
|
|
||||||
const { translate } = useTranslations();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const handleNext = () => navigate(routes.client.priceList());
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={`${styles.page} page`}>
|
|
||||||
<SpecialWelcomeOffer open={isOpenModal} onClose={onCloseSpecialOffer} />
|
|
||||||
<img className={styles.icon} src="/stop-icon.webp" alt="stop" />
|
|
||||||
<Title variant="h2">{translate("aura.attention.title")}</Title>
|
|
||||||
<p className={styles.text}>{translate("aura.warming_up.body")}</p>
|
|
||||||
<div className={styles["buttons-container"]}>
|
|
||||||
<MainButton onClick={handleNext}>
|
|
||||||
{translate("aura.warmin_good.button")}
|
|
||||||
</MainButton>
|
|
||||||
<MainButton onClick={handleNext}>
|
|
||||||
{translate("aura.warmin_bad.button")}
|
|
||||||
</MainButton>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AttentionPage;
|
|
||||||
@ -1,33 +0,0 @@
|
|||||||
.page {
|
|
||||||
position: relative;
|
|
||||||
flex: auto;
|
|
||||||
height: calc(100vh - 50px);
|
|
||||||
max-height: -webkit-fill-available;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon {
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text {
|
|
||||||
text-align: center;
|
|
||||||
line-height: 1.2;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons-container {
|
|
||||||
width: 100%;
|
|
||||||
margin: 64px auto 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button {
|
|
||||||
white-space: pre;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import MainButton from "@/components/MainButton";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
|
|
||||||
interface IAppleAuthButtonProps {
|
|
||||||
onClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AppleAuthButton({ onClick }: IAppleAuthButtonProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<MainButton className={styles.button} onClick={onClick}>
|
|
||||||
<img src="apple-auth-icon.webp" alt="Apple" />
|
|
||||||
{"Sign in with Apple"}
|
|
||||||
</MainButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppleAuthButton;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.button {
|
|
||||||
width: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #000;
|
|
||||||
font-size: 19px;
|
|
||||||
font-weight: 600;
|
|
||||||
border: solid #000 2px;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
@ -1,17 +0,0 @@
|
|||||||
import MainButton from "@/components/MainButton";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
|
|
||||||
interface IGoogleAuthButtonProps {
|
|
||||||
onClick: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AppleAuthButton({ onClick }: IGoogleAuthButtonProps): JSX.Element {
|
|
||||||
return (
|
|
||||||
<MainButton className={styles.button} onClick={onClick}>
|
|
||||||
<img src="google-auth-icon.svg" alt="Google" />
|
|
||||||
{"Sign in with Google"}
|
|
||||||
</MainButton>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AppleAuthButton;
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.button {
|
|
||||||
width: 100%;
|
|
||||||
background-color: transparent;
|
|
||||||
color: #000;
|
|
||||||
font-size: 19px;
|
|
||||||
font-weight: 600;
|
|
||||||
border: solid #4285f4 2px;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-start;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
@ -1,96 +0,0 @@
|
|||||||
import Policy from "../Policy";
|
|
||||||
import { useTranslations } from "@/hooks/translations";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
import AppleAuthButton from "./AppleAuthButton";
|
|
||||||
import routes from "@/routes";
|
|
||||||
import Title from "../Title";
|
|
||||||
import { APNG } from "apng-js";
|
|
||||||
import Player from "apng-js/types/library/player";
|
|
||||||
import { useEffect, useRef } from "react";
|
|
||||||
import GoogleAuthButton from "./GoogleAuthButton";
|
|
||||||
|
|
||||||
let apngPlayer: Player | null = null;
|
|
||||||
|
|
||||||
interface AuthPageProps {
|
|
||||||
padLockApng: Error | APNG;
|
|
||||||
}
|
|
||||||
|
|
||||||
function AuthPage({ padLockApng }: AuthPageProps): JSX.Element {
|
|
||||||
const { translate } = useTranslations();
|
|
||||||
const padLockCanvasRef = useRef<HTMLCanvasElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let padLockTimeOut: NodeJS.Timeout;
|
|
||||||
async function getApngPlayer() {
|
|
||||||
const context = padLockCanvasRef.current?.getContext("2d");
|
|
||||||
if (context && !(padLockApng instanceof Error)) {
|
|
||||||
context.canvas.height = padLockApng.height;
|
|
||||||
context.canvas.width = padLockApng.width;
|
|
||||||
const _apngPlayer = await padLockApng.getPlayer(context);
|
|
||||||
apngPlayer = _apngPlayer;
|
|
||||||
if (apngPlayer) {
|
|
||||||
apngPlayer.play();
|
|
||||||
padLockTimeOut = setTimeout(() => {
|
|
||||||
if (apngPlayer) {
|
|
||||||
apngPlayer.pause();
|
|
||||||
}
|
|
||||||
}, 900);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
getApngPlayer();
|
|
||||||
return () => {
|
|
||||||
clearTimeout(padLockTimeOut);
|
|
||||||
};
|
|
||||||
}, [padLockApng]);
|
|
||||||
|
|
||||||
const handleAppleAuth = async () => {
|
|
||||||
window.location.href = routes.server.appleAuth(
|
|
||||||
encodeURI(`${window.location.origin}/auth/result/`)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleGoogleAuth = async () => {
|
|
||||||
window.location.href = routes.server.googleAuth(
|
|
||||||
encodeURI(`${window.location.origin}/auth/result/`)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className={`${styles.page} page`}>
|
|
||||||
<Title variant="h2" className={styles.title}>
|
|
||||||
Sign in to save your energy analysis, horoscope, and predictions.
|
|
||||||
</Title>
|
|
||||||
<canvas className={styles["pad-lock"]} ref={padLockCanvasRef} />
|
|
||||||
<p className={styles.disclaimer}>{translate("we_dont_share")}</p>
|
|
||||||
<div className={styles["buttons-container"]}>
|
|
||||||
<GoogleAuthButton onClick={handleGoogleAuth} />
|
|
||||||
<AppleAuthButton onClick={handleAppleAuth} />
|
|
||||||
</div>
|
|
||||||
<Policy className={styles.policy} sizing="medium">
|
|
||||||
{translate("_continue_agree", {
|
|
||||||
eulaLink: (
|
|
||||||
<a
|
|
||||||
href="https://aura.wit.life/terms"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{translate("eula")}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
privacyLink: (
|
|
||||||
<a
|
|
||||||
href="https://aura.wit.life/privacy"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
>
|
|
||||||
{translate("privacy_policy")}
|
|
||||||
</a>
|
|
||||||
),
|
|
||||||
})}
|
|
||||||
</Policy>
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AuthPage;
|
|
||||||
@ -1,41 +0,0 @@
|
|||||||
.page {
|
|
||||||
position: relative;
|
|
||||||
/* height: calc(100vh - 103px);
|
|
||||||
max-height: -webkit-fill-available; */
|
|
||||||
flex: auto;
|
|
||||||
justify-content: flex-start;
|
|
||||||
display: flex;
|
|
||||||
grid-template-rows: 1fr 96px;
|
|
||||||
justify-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.disclaimer {
|
|
||||||
font-size: 19px;
|
|
||||||
font-weight: 500;
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 24px;
|
|
||||||
line-height: 150%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
|
||||||
font-weight: 700;
|
|
||||||
margin: 32px 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.pad-lock {
|
|
||||||
width: 76px;
|
|
||||||
margin-top: 48px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.buttons-container {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
max-width: 320px;
|
|
||||||
margin-top: 42px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.policy {
|
|
||||||
margin-top: 32px;
|
|
||||||
}
|
|
||||||
@ -6,7 +6,7 @@ function AdviceFromAstrologer() {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<TextWithEmoji text="1:1 Advice from your personal astrologer" emoji="sparkling-heart.svg" />
|
<TextWithEmoji text="1:1 Advice from your personal astrologer" emoji="sparkling-heart.svg" />
|
||||||
<img className={styles.messages} src={images("messages.png")} alt="messages" />
|
<img className={styles.messages} src={images("messages.svg")} alt="messages" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,8 @@
|
|||||||
margin-top: 26px;
|
margin-top: 26px;
|
||||||
box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.46),
|
box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.46),
|
||||||
inset 6px 6px 82px 0px rgba(0, 0, 0, 0.25);
|
inset 6px 6px 82px 0px rgba(0, 0, 0, 0.25);
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
backface-visibility: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.circularText {
|
.circularText {
|
||||||
@ -17,20 +19,34 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
animation: rotate 20s linear infinite;
|
animation: rotate 20s linear infinite;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
|
-webkit-perspective: 1000;
|
||||||
|
-webkit-backface-visibility: hidden;
|
||||||
|
|
||||||
svg {
|
svg {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
-webkit-transform: translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
text {
|
text {
|
||||||
fill: white;
|
fill: white;
|
||||||
font-size: 8px;
|
font-size: 8px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
|
||||||
&:nth-of-type(2) {
|
&:nth-of-type(2) {
|
||||||
transform: rotate(180deg);
|
-webkit-transform: rotate(180deg) translateZ(0);
|
||||||
transform-origin: center;
|
-moz-transform: rotate(180deg) translateZ(0);
|
||||||
|
-ms-transform: rotate(180deg) translateZ(0);
|
||||||
|
-o-transform: rotate(180deg) translateZ(0);
|
||||||
|
transform: rotate(180deg) translateZ(0);
|
||||||
|
|
||||||
|
-webkit-transform-origin: center center;
|
||||||
|
-moz-transform-origin: center center;
|
||||||
|
-ms-transform-origin: center center;
|
||||||
|
-o-transform-origin: center center;
|
||||||
|
transform-origin: center center;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -45,10 +61,22 @@
|
|||||||
|
|
||||||
@keyframes rotate {
|
@keyframes rotate {
|
||||||
from {
|
from {
|
||||||
transform: rotate(0deg);
|
-webkit-transform: rotate(0deg) translateZ(0);
|
||||||
|
transform: rotate(0deg) translateZ(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
to {
|
to {
|
||||||
transform: rotate(360deg);
|
-webkit-transform: rotate(360deg) translateZ(0);
|
||||||
|
transform: rotate(360deg) translateZ(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@-webkit-keyframes rotate {
|
||||||
|
from {
|
||||||
|
-webkit-transform: rotate(0deg) translateZ(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
to {
|
||||||
|
-webkit-transform: rotate(360deg) translateZ(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -6,7 +6,7 @@ function FindingPartner() {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<TextWithEmoji text="Finding the most compatible partner" emoji="revolving-hearts.svg" />
|
<TextWithEmoji text="Finding the most compatible partner" emoji="revolving-hearts.svg" />
|
||||||
<img className={styles.smartphone} src={images("smartphone.png")} alt="smartphone" />
|
<img className={styles.smartphone} src={images("smartphone.svg")} alt="smartphone" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import styles from "./styles.module.scss";
|
|||||||
function Payments() {
|
function Payments() {
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<img src={images("payments.svg")} alt="payments" />
|
<img src={images("payments.png")} alt="payments" />
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@ import { useNavigate } from "react-router-dom";
|
|||||||
import routes from "@/routes";
|
import routes from "@/routes";
|
||||||
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
|
import BlurComponent from "@/components/BlurComponent";
|
||||||
|
|
||||||
const features = [
|
const features = [
|
||||||
{
|
{
|
||||||
@ -171,9 +172,13 @@ function MarketingLanding() {
|
|||||||
</div>
|
</div>
|
||||||
<GuaranteedSecurityPayments />
|
<GuaranteedSecurityPayments />
|
||||||
<Payments />
|
<Payments />
|
||||||
<Button className={styles.buttonContinue} onClick={handleContinue}>
|
<div className={styles.buttonContainer}>
|
||||||
Continue
|
<BlurComponent isActiveBlur={true}>
|
||||||
</Button>
|
<Button className={styles.buttonContinue} onClick={handleContinue}>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</BlurComponent>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,7 +6,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 460px;
|
max-width: 460px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
padding-bottom: 64px;
|
padding-bottom: 140px;
|
||||||
overflow-x: hidden;
|
overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,11 +67,6 @@
|
|||||||
padding-left: 34px;
|
padding-left: 34px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.buttonContinue {
|
|
||||||
max-width: 300px;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.relative-container {
|
.relative-container {
|
||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
@ -152,4 +147,23 @@
|
|||||||
.backgroundElement14 {
|
.backgroundElement14 {
|
||||||
bottom: -50px;
|
bottom: -50px;
|
||||||
right: -30px;
|
right: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
.buttonContinue {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
margin-top: 48px;
|
||||||
|
margin-bottom: 64px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -8,15 +8,21 @@ import { actions, selectors } from "@/store";
|
|||||||
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import PaymentModal from "@/components/PaymentModal";
|
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import BlurComponent from "@/components/BlurComponent";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.email.marketing"];
|
||||||
|
|
||||||
function SpecialOffer() {
|
function SpecialOffer() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
|
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
|
||||||
const activeProduct = useSelector(selectors.selectActiveProduct);
|
const activeProduct = useSelector(selectors.selectActiveProduct);
|
||||||
|
|
||||||
const { products, getText } = usePaywall({
|
const { products, getText } = usePaywall({
|
||||||
placementKey: EPlacementKeys["aura.placement.email.marketing"],
|
placementKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const trialPrice = ((products[0]?.trialPrice || 0) / 100).toFixed(2) || 0;
|
const trialPrice = ((products[0]?.trialPrice || 0) / 100).toFixed(2) || 0;
|
||||||
@ -27,7 +33,7 @@ function SpecialOffer() {
|
|||||||
dispatch(actions.payment.update({ activeProduct: products[0] }));
|
dispatch(actions.payment.update({ activeProduct: products[0] }));
|
||||||
}, [dispatch, products]);
|
}, [dispatch, products]);
|
||||||
|
|
||||||
const openStripeModal = () => {
|
const openPaymentModal = () => {
|
||||||
setIsOpenPaymentModal(true);
|
setIsOpenPaymentModal(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,6 +41,14 @@ function SpecialOffer() {
|
|||||||
setIsOpenPaymentModal(false);
|
setIsOpenPaymentModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onPaymentError = () => {
|
||||||
|
return navigate(routes.client.paymentFail())
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPaymentSuccess = () => {
|
||||||
|
return navigate(routes.client.paymentSuccess())
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{products[0] && (
|
{products[0] && (
|
||||||
@ -44,8 +58,10 @@ function SpecialOffer() {
|
|||||||
onClose={handleCloseModal}
|
onClose={handleCloseModal}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
>
|
>
|
||||||
<PaymentModal
|
<PaymentForm
|
||||||
placementKey={EPlacementKeys["aura.placement.email.marketing"]}
|
placementKey={placementKey}
|
||||||
|
onPaymentError={onPaymentError}
|
||||||
|
onPaymentSuccess={onPaymentSuccess}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
@ -67,12 +83,16 @@ function SpecialOffer() {
|
|||||||
trialDuration={trialDuration}
|
trialDuration={trialDuration}
|
||||||
saveText={getText("text.save") as string}
|
saveText={getText("text.save") as string}
|
||||||
/>
|
/>
|
||||||
<Button className={styles.button} onClick={openStripeModal}>
|
|
||||||
Continue
|
|
||||||
</Button>
|
|
||||||
<p className={styles.contentPolicy}>
|
<p className={styles.contentPolicy}>
|
||||||
By continuing you agree that if you don't cancel prior to the end of the {trialDuration}-days trial, you will automatically be charged ${price} every 2 weeks until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms
|
By continuing you agree that if you don't cancel prior to the end of the {trialDuration}-days trial, you will automatically be charged ${price} every 2 weeks until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms
|
||||||
</p>
|
</p>
|
||||||
|
<div className={styles.buttonContainer}>
|
||||||
|
<BlurComponent isActiveBlur={true}>
|
||||||
|
<Button className={styles.button} onClick={openPaymentModal}>
|
||||||
|
Continue
|
||||||
|
</Button>
|
||||||
|
</BlurComponent>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -25,7 +25,7 @@
|
|||||||
border-radius: 30px 30px 0 0;
|
border-radius: 30px 30px 0 0;
|
||||||
min-height: calc(100dvh - 39px - 26px * 1.25 - 29px);
|
min-height: calc(100dvh - 39px - 26px * 1.25 - 29px);
|
||||||
margin-top: 29px;
|
margin-top: 29px;
|
||||||
padding: 53px 18px 46px;
|
padding: 53px 18px 160px;
|
||||||
color: #000;
|
color: #000;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@ -51,7 +51,7 @@
|
|||||||
line-height: 125%;
|
line-height: 125%;
|
||||||
font-weight: 300;
|
font-weight: 300;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
margin-top: 61px;
|
margin-top: 36px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,4 +59,23 @@
|
|||||||
margin-top: 59px;
|
margin-top: 59px;
|
||||||
max-width: 307px;
|
max-width: 307px;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.buttonContainer {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1000;
|
||||||
|
max-width: 300px;
|
||||||
|
margin-top: 24px;
|
||||||
|
margin-bottom: 64px;
|
||||||
|
pointer-events: all;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,43 +1,43 @@
|
|||||||
import { useTranslations } from '@/hooks/translations';
|
import { useTranslations } from '@/hooks/translations';
|
||||||
import { addCurrency, ELocalesPlacement } from '@/locales';
|
import { addCurrency, ELocalesPlacement } from '@/locales';
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { LegacyRef, useEffect, useRef } from 'react';
|
import { LegacyRef, useRef, useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { selectors } from '@/store';
|
import { selectors } from '@/store';
|
||||||
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
|
import { EPlacementKeys, IPaywallProduct } from '@/api/resources/Paywall';
|
||||||
import Loader from '@/components/Loader';
|
|
||||||
import cn from "classnames";
|
import cn from "classnames";
|
||||||
import { getFormattedPrice } from '@/utils/price.utils';
|
import { getFormattedPrice } from '@/utils/price.utils';
|
||||||
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
|
import SecurityPayments from '@/components/pages/TrialPayment/components/SecurityPayments';
|
||||||
import { usePayment } from '@/hooks/payment/nmi/usePayment';
|
|
||||||
import Title from '@/components/Title';
|
import Title from '@/components/Title';
|
||||||
import metricService, { EGoals, EMetrics } from '@/services/metric/metricService';
|
import metricService, { EGoals, EMetrics } from '@/services/metric/metricService';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import routes from '@/routes';
|
import routes from '@/routes';
|
||||||
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
|
import CreditCardIcon from '@/components/PaymentModalNew/PaymentCardModal/CreditCardIcon';
|
||||||
|
import Modal from '@/components/Modal';
|
||||||
|
import NMIPaymentForm from '@/components/Payment/nmi/PaymentForm';
|
||||||
|
|
||||||
|
|
||||||
interface IPaymentFormProps {
|
interface IPaymentFormProps {
|
||||||
placementKey: EPlacementKeys;
|
|
||||||
activeProduct: IPaywallProduct;
|
activeProduct: IPaywallProduct;
|
||||||
}
|
}
|
||||||
|
|
||||||
function PaymentForm({
|
function PaymentForm({
|
||||||
activeProduct,
|
activeProduct,
|
||||||
placementKey,
|
|
||||||
}: IPaymentFormProps) {
|
}: IPaymentFormProps) {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
||||||
const ref = useRef<HTMLDivElement>();
|
const ref = useRef<HTMLDivElement>();
|
||||||
const currency = useSelector(selectors.selectCurrency);
|
const currency = useSelector(selectors.selectCurrency);
|
||||||
|
const [isPaymentSuccess, setIsPaymentSuccess] = useState(false);
|
||||||
|
const [isPaymentError, setIsPaymentError] = useState(false);
|
||||||
|
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||||
|
|
||||||
const { isLoading, error, isPaymentSuccess, showCreditCardForm } = usePayment({
|
const onPaymentError = () => {
|
||||||
placementKey,
|
setIsPaymentError(true);
|
||||||
activeProduct
|
}
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const onPaymentSuccess = () => {
|
||||||
if (!isPaymentSuccess) return;
|
setIsPaymentSuccess(true);
|
||||||
metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
|
metricService.reachGoal(EGoals.PAYMENT_SUCCESS);
|
||||||
metricService.reachGoal(EGoals.PAYMENT_SUCCESS_PALMISTRY, [
|
metricService.reachGoal(EGoals.PAYMENT_SUCCESS_PALMISTRY, [
|
||||||
EMetrics.YANDEX,
|
EMetrics.YANDEX,
|
||||||
@ -48,13 +48,12 @@ function PaymentForm({
|
|||||||
value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
|
value: ((activeProduct.trialPrice || 100) / 100).toFixed(2),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const redirectTimeout = setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate(routes.client.skipTrial());
|
navigate(routes.client.skipTrial());
|
||||||
}, 1500);
|
}, 1500);
|
||||||
return () => clearTimeout(redirectTimeout);
|
}
|
||||||
}, [isPaymentSuccess])
|
|
||||||
|
|
||||||
if (error?.length) {
|
if (isPaymentError) {
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={ref as LegacyRef<HTMLDivElement>}
|
ref={ref as LegacyRef<HTMLDivElement>}
|
||||||
@ -97,15 +96,13 @@ function PaymentForm({
|
|||||||
<div
|
<div
|
||||||
ref={ref as LegacyRef<HTMLDivElement>}
|
ref={ref as LegacyRef<HTMLDivElement>}
|
||||||
className={cn(
|
className={cn(
|
||||||
styles.paymentModalContainer,
|
styles.paymentModalContainer
|
||||||
isLoading && styles.paymentModalContainerLoading
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{isLoading && (
|
|
||||||
<div className={cn(styles.paymentModalLoader)}>
|
<Modal containerClassName={styles["modal-content"]} open={isPaymentModalOpen}>
|
||||||
<Loader />
|
<NMIPaymentForm onPaymentError={onPaymentError} onPaymentSuccess={onPaymentSuccess} placementKey={EPlacementKeys['aura.placement.palmistry.redesign']} />
|
||||||
</div>
|
</Modal>
|
||||||
)}
|
|
||||||
|
|
||||||
<div className={styles.paymentModalPrice}>
|
<div className={styles.paymentModalPrice}>
|
||||||
{translate(
|
{translate(
|
||||||
@ -119,29 +116,25 @@ function PaymentForm({
|
|||||||
ELocalesPlacement.PalmistryV1
|
ELocalesPlacement.PalmistryV1
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
{!isLoading && (
|
<div
|
||||||
<>
|
className={styles.paymentCreditCard}
|
||||||
<div
|
onClick={() => setIsPaymentModalOpen(true)}
|
||||||
className={styles.paymentCreditCard}
|
>
|
||||||
onClick={showCreditCardForm}
|
<CreditCardIcon />
|
||||||
>
|
<div>Credit / Debit Card</div>
|
||||||
<CreditCardIcon />
|
</div>
|
||||||
<div>Credit / Debit Card</div>
|
{/* <GooglePayButton />
|
||||||
</div>
|
|
||||||
{/* <GooglePayButton />
|
|
||||||
<ApplePayButton /> */}
|
<ApplePayButton /> */}
|
||||||
<div className={styles.infoContainer}>
|
<div className={styles.infoContainer}>
|
||||||
<SecurityPayments />
|
<SecurityPayments />
|
||||||
<p className={styles.address}>
|
<p className={styles.address}>
|
||||||
{translate(
|
{translate(
|
||||||
"payment_modal.address",
|
"payment_modal.address",
|
||||||
undefined,
|
undefined,
|
||||||
ELocalesPlacement.V1
|
ELocalesPlacement.V1
|
||||||
)}
|
)}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -86,4 +86,9 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
color: #121620;
|
color: #121620;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-content {
|
||||||
|
overflow-x: hidden;
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -3,7 +3,6 @@ import { HTMLAttributes } from 'react';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import PaymentForm from './PaymentForm';
|
import PaymentForm from './PaymentForm';
|
||||||
import { EPlacementKeys } from '@/api/resources/Paywall';
|
|
||||||
|
|
||||||
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
|
function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
|
||||||
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
|
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
|
||||||
@ -21,9 +20,6 @@ function PaymentModalV1(props: HTMLAttributes<HTMLDivElement>) {
|
|||||||
>
|
>
|
||||||
<PaymentForm
|
<PaymentForm
|
||||||
activeProduct={activeProductFromStore}
|
activeProduct={activeProductFromStore}
|
||||||
placementKey={
|
|
||||||
EPlacementKeys["aura.placement.palmistry.redesign"]
|
|
||||||
}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -34,12 +34,12 @@ function PaymentTable({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const getPrice = useCallback(
|
const getPrice = useCallback(
|
||||||
(product: IPaywallProduct) => {
|
(price: number) => {
|
||||||
if ((product.trialPrice || 0) % 100 === 0) {
|
if (price % 100 === 0) {
|
||||||
return addCurrency((product.trialPrice || 0) / 100, currency);
|
return addCurrency(price / 100, currency);
|
||||||
}
|
}
|
||||||
return addCurrency(
|
return addCurrency(
|
||||||
((product.trialPrice || 0) / 100).toFixed(2),
|
(price / 100).toFixed(2),
|
||||||
currency
|
currency
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -78,13 +78,13 @@ function PaymentTable({
|
|||||||
{/* {translate("/trial-payment.payment_table.title", {
|
{/* {translate("/trial-payment.payment_table.title", {
|
||||||
price: <span className={styles.purple}>{getPrice(product)}</span>,
|
price: <span className={styles.purple}>{getPrice(product)}</span>,
|
||||||
})} */}
|
})} */}
|
||||||
Personalized plan for <span className={styles.blue}>{getPrice(product)}</span>
|
Personalized plan for <span className={styles.blue}>{getPrice(product.trialPrice || 0)}</span>
|
||||||
</Title>
|
</Title>
|
||||||
<div className={styles["table-element"]}>
|
<div className={styles["table-element"]}>
|
||||||
<p className={styles["total-today"]}>
|
<p className={styles["total-today"]}>
|
||||||
{translate("/trial-payment.payment_table.total_today")}
|
{translate("/trial-payment.payment_table.total_today")}
|
||||||
</p>
|
</p>
|
||||||
<span>{getPrice(product)}</span>
|
<span>{getPrice(product.trialPrice || 0)}</span>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className={styles["table-element"]}>
|
<div className={styles["table-element"]}>
|
||||||
@ -109,9 +109,9 @@ function PaymentTable({
|
|||||||
)} */}
|
)} */}
|
||||||
<div>
|
<div>
|
||||||
<span className={styles.discount}>
|
<span className={styles.discount}>
|
||||||
{addCurrency(Number(getText("full.price")) / 100, currency)}
|
{addCurrency(Number(getText("full.price")), currency)}
|
||||||
</span>
|
</span>
|
||||||
<span className={styles["discount-price"]}>{getPrice(product)}</span>
|
<span className={styles["discount-price"]}>{getPrice(product.price)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -129,7 +129,7 @@ function PaymentTable({
|
|||||||
),
|
),
|
||||||
trialDuration: product.trialDuration,
|
trialDuration: product.trialDuration,
|
||||||
price: addCurrency(product.price / 100, currency),
|
price: addCurrency(product.price / 100, currency),
|
||||||
trialPrice: getPrice(product),
|
trialPrice: getPrice(product.trialPrice || 0),
|
||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,8 +1,34 @@
|
|||||||
import Title from "@/components/Title";
|
import Title from "@/components/Title";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { images } from "../../data";
|
import { images } from "../../data";
|
||||||
|
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
||||||
|
import { addCurrency, ELocalesPlacement } from "@/locales";
|
||||||
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
|
import { Currency } from "@/components/PaymentTable";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.email.palmistry.discount"]
|
||||||
|
|
||||||
|
const getPrice = (price: number, currency: Currency) => {
|
||||||
|
if (price % 100 === 0) {
|
||||||
|
return addCurrency(price / 100, currency);
|
||||||
|
}
|
||||||
|
return addCurrency(
|
||||||
|
(price / 100).toFixed(2),
|
||||||
|
currency
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function SecretDiscountTable() {
|
function SecretDiscountTable() {
|
||||||
|
const { products, currency, getText } = usePaywall({
|
||||||
|
placementKey,
|
||||||
|
localesPlacement: ELocalesPlacement.PalmistryV1,
|
||||||
|
});
|
||||||
|
|
||||||
|
const activeProduct = products[0];
|
||||||
|
const price = activeProduct?.price || 0;
|
||||||
|
const trialPrice = activeProduct?.trialPrice || 0;
|
||||||
|
const trialDuration = activeProduct?.trialDuration || 7;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.container}>
|
<div className={styles.container}>
|
||||||
<Title className={styles.title} variant="h3">
|
<Title className={styles.title} variant="h3">
|
||||||
@ -14,21 +40,21 @@ function SecretDiscountTable() {
|
|||||||
<Title className={styles.title} variant="h4">
|
<Title className={styles.title} variant="h4">
|
||||||
Secret discount applied!
|
Secret discount applied!
|
||||||
</Title>
|
</Title>
|
||||||
<span className={styles["old-discount"]}>-30%</span>
|
<span className={styles["old-discount"]}>{getText("old.discount")}</span>
|
||||||
<span className={styles["new-discount"]}>-50%</span>
|
<span className={styles["new-discount"]}>{getText("new.discount")}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles["grid-line"]} ${styles["days-14"]}`}>
|
<div className={`${styles["grid-line"]} ${styles["days-14"]}`}>
|
||||||
<p>Your cost per 14 days after trial:</p>
|
<p>Your cost per {trialDuration} days after trial:</p>
|
||||||
<span className={styles["old-price"]}>$19</span>
|
<span className={styles["old-price"]}>{addCurrency(Number(getText("old.price")), currency)}</span>
|
||||||
<span className={styles["new-price"]}>$19</span>
|
<span className={styles["new-price"]}>{getPrice(price, currency)}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className={`${styles["grid-line"]} ${styles["save"]}`}>
|
<div className={`${styles["grid-line"]} ${styles["save"]}`}>
|
||||||
<p>You save $30</p>
|
<p>You save {addCurrency(Number(getText("save")), currency)}</p>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<div className={`${styles["grid-line"]} ${styles["total-today"]}`}>
|
<div className={`${styles["grid-line"]} ${styles["total-today"]}`}>
|
||||||
<p>Total today</p>
|
<p>Total today</p>
|
||||||
<span>$9</span>
|
<span>{getPrice(trialPrice, currency)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -10,9 +10,11 @@ import { usePaywall } from "@/hooks/paywall/usePaywall";
|
|||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
import { ELocalesPlacement } from "@/locales";
|
import { ELocalesPlacement } from "@/locales";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.email.palmistry.discount"]
|
||||||
|
|
||||||
function SaveOff() {
|
function SaveOff() {
|
||||||
const { products } = usePaywall({
|
const { products, getText } = usePaywall({
|
||||||
placementKey: EPlacementKeys["aura.placement.email.marketing"],
|
placementKey,
|
||||||
localesPlacement: ELocalesPlacement.PalmistryV1,
|
localesPlacement: ELocalesPlacement.PalmistryV1,
|
||||||
});
|
});
|
||||||
const activeProduct = products[0]
|
const activeProduct = products[0]
|
||||||
@ -31,16 +33,16 @@ function SaveOff() {
|
|||||||
<Blob2 className={styles.blob2} />
|
<Blob2 className={styles.blob2} />
|
||||||
<img className={styles.gift} src={images("gift.svg")} alt="gift" />
|
<img className={styles.gift} src={images("gift.svg")} alt="gift" />
|
||||||
<Title className={styles.title}>
|
<Title className={styles.title}>
|
||||||
SAVE 70% OFF!
|
SAVE {getText("discount")}% OFF!
|
||||||
</Title>
|
</Title>
|
||||||
<p className={styles.description}>
|
<p className={styles.description}>
|
||||||
<span className={styles.price}>${price}</span> instead <span className={styles.discount}>of $65</span>
|
<span className={styles.price}>${price}</span> instead <span className={styles.discount}>of ${getText("full.price")}</span>
|
||||||
</p>
|
</p>
|
||||||
<p className={styles.point} style={{ marginTop: 12 }}>
|
<p className={styles.point} style={{ marginTop: 12 }}>
|
||||||
<img src={images("fire.png")} alt="fire" /> {trialDuration}-day trial
|
<img src={images("fire.png")} alt="fire" /> {trialDuration}-day trial
|
||||||
</p>
|
</p>
|
||||||
<p className={styles.point}>
|
<p className={styles.point}>
|
||||||
<img src={images("gift.png")} alt="gift" /> 70% off on your personalized plan
|
<img src={images("gift.png")} alt="gift" /> {getText("discount")}% off on your personalized plan
|
||||||
</p>
|
</p>
|
||||||
<Button className={styles.button} onClick={handleNext}>
|
<Button className={styles.button} onClick={handleNext}>
|
||||||
GET {trialDuration}-day trial
|
GET {trialDuration}-day trial
|
||||||
|
|||||||
@ -8,53 +8,84 @@ import { usePaywall } from "@/hooks/paywall/usePaywall";
|
|||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
import { ELocalesPlacement } from "@/locales";
|
import { ELocalesPlacement } from "@/locales";
|
||||||
import Modal from "@/components/Modal";
|
import Modal from "@/components/Modal";
|
||||||
import PaymentModal from "@/components/PaymentModal";
|
import { useEffect, useState } from "react";
|
||||||
import { useState } from "react";
|
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import routes from "@/routes";
|
||||||
|
import { useDispatch } from "react-redux";
|
||||||
|
import { actions } from "@/store";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.email.palmistry.discount"]
|
||||||
|
|
||||||
function SecretDiscount() {
|
function SecretDiscount() {
|
||||||
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
|
const dispatch = useDispatch();
|
||||||
|
const navigate = useNavigate();
|
||||||
const { products } = usePaywall({
|
const { products } = usePaywall({
|
||||||
placementKey: EPlacementKeys["aura.placement.email.marketing"],
|
placementKey,
|
||||||
localesPlacement: ELocalesPlacement.PalmistryV1,
|
localesPlacement: ELocalesPlacement.PalmistryV1,
|
||||||
});
|
});
|
||||||
const activeProduct = products[0]
|
|
||||||
const price = (activeProduct?.price || 0) / 100
|
|
||||||
const trialDuration = activeProduct?.trialDuration || 7
|
|
||||||
|
|
||||||
const openStripeModal = () => {
|
const activeProduct = products[0];
|
||||||
setIsOpenPaymentModal(true);
|
const price = (activeProduct?.price || 0) / 100;
|
||||||
};
|
const trialDuration = activeProduct?.trialDuration || 7;
|
||||||
|
|
||||||
const handleCloseModal = () => {
|
useEffect(() => {
|
||||||
setIsOpenPaymentModal(false);
|
if (!activeProduct) return;
|
||||||
|
dispatch(actions.payment.update({
|
||||||
|
activeProduct
|
||||||
|
}))
|
||||||
|
}, [activeProduct])
|
||||||
|
|
||||||
|
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||||
|
|
||||||
|
|
||||||
|
const onPaymentSuccess = () => {
|
||||||
|
return navigate(routes.client.paymentSuccess())
|
||||||
|
}
|
||||||
|
|
||||||
|
const onModalClosed = () => {
|
||||||
|
setIsPaymentModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPaymentError = () => {
|
||||||
|
return navigate(routes.client.paymentFail())
|
||||||
|
}
|
||||||
|
|
||||||
|
const openPaymentModal = () => {
|
||||||
|
setIsPaymentModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{products[0] && (
|
{activeProduct && (
|
||||||
<Modal
|
<Modal
|
||||||
containerClassName={styles.modal}
|
containerClassName={styles.modal}
|
||||||
open={isOpenPaymentModal}
|
open={isPaymentModalOpen}
|
||||||
onClose={handleCloseModal}
|
onClose={onModalClosed}
|
||||||
type="hidden"
|
type="hidden"
|
||||||
>
|
>
|
||||||
<PaymentModal
|
<PaymentForm
|
||||||
placementKey={EPlacementKeys["aura.placement.email.marketing"]}
|
placementKey={placementKey}
|
||||||
|
onPaymentError={onPaymentError}
|
||||||
|
onPaymentSuccess={onPaymentSuccess}
|
||||||
/>
|
/>
|
||||||
</Modal>
|
</Modal>
|
||||||
)}
|
)}
|
||||||
<Blob3 className={styles.blob3} />
|
<Blob3 className={styles.blob3} />
|
||||||
<Blob4 className={styles.blob4} />
|
|
||||||
<Title className={styles.title} variant="h1">
|
<Title className={styles.title} variant="h1">
|
||||||
You get a secret discount!
|
You get a secret discount!
|
||||||
</Title>
|
</Title>
|
||||||
<SecretDiscountTable />
|
<SecretDiscountTable />
|
||||||
<Button className={styles.button} onClick={openStripeModal}>
|
<Button className={styles.button} onClick={openPaymentModal}>
|
||||||
GET {trialDuration}-DAY TRIAL
|
GET {trialDuration}-DAY TRIAL
|
||||||
</Button>
|
</Button>
|
||||||
<p className={styles.policy}>
|
|
||||||
By continuing you agree that if you don't cancel prior to the end of the {trialDuration}-days trial, you will automatically be charged ${price} for the introductory period of 14 days thereafter the standard rate of ${price} every 14 days until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms.
|
<div className={styles["policy-container"]}>
|
||||||
</p>
|
<p className={styles.policy}>
|
||||||
|
By continuing you agree that if you don't cancel prior to the end of the {trialDuration}-days trial, you will automatically be charged ${price} for the introductory period of 14 days thereafter the standard rate of ${price} every 14 days until you cancel in settings. Learn more about cancellation and refund policy in Subscription terms.
|
||||||
|
</p>
|
||||||
|
<Blob4 className={styles.blob4} />
|
||||||
|
</div>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,6 @@
|
|||||||
z-index: -1;
|
z-index: -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.blob4 {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
padding: 15px 0;
|
padding: 15px 0;
|
||||||
background-color: #F096C4;
|
background-color: #F096C4;
|
||||||
@ -33,16 +26,26 @@
|
|||||||
margin-top: 30px;
|
margin-top: 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.policy {
|
.policy-container {
|
||||||
width: 100%;
|
position: relative;
|
||||||
position: absolute;
|
width: calc(100% + 52px);
|
||||||
left: 50%;
|
height: fit-content;
|
||||||
transform: translateX(-50%);
|
bottom: -58px;
|
||||||
bottom: 0;
|
|
||||||
margin-bottom: 34px;
|
&>.policy {
|
||||||
padding: 0 14px;
|
width: 100%;
|
||||||
font-size: 13px;
|
margin: 34px 0;
|
||||||
font-weight: 400;
|
padding: 0 14px;
|
||||||
line-height: 130%;
|
font-size: 13px;
|
||||||
color: #fff;
|
font-weight: 400;
|
||||||
|
line-height: 130%;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.blob4 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -8,26 +8,32 @@ import { ELocalesPlacement } from "@/locales";
|
|||||||
import { images } from "../../data";
|
import { images } from "../../data";
|
||||||
import DiscountExpires from "../../components/DiscountExpires";
|
import DiscountExpires from "../../components/DiscountExpires";
|
||||||
import PaymentTable from "../../components/PaymentTable";
|
import PaymentTable from "../../components/PaymentTable";
|
||||||
import { useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
import { selectors } from "@/store";
|
import { actions, selectors } from "@/store";
|
||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
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 { useMemo } from "react";
|
import { 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";
|
||||||
import Reviews from "../../components/Reviews";
|
import Reviews from "../../components/Reviews";
|
||||||
import Address from "../../components/Address";
|
import Address from "../../components/Address";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.email.palmistry"];
|
||||||
|
|
||||||
function TrialPayment() {
|
function TrialPayment() {
|
||||||
|
const dispatch = useDispatch();
|
||||||
// const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
// const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
|
||||||
const { products } = usePaywall({
|
const { products } = usePaywall({
|
||||||
placementKey: EPlacementKeys["aura.placement.email.marketing"],
|
placementKey,
|
||||||
localesPlacement: ELocalesPlacement.PalmistryV1,
|
localesPlacement: ELocalesPlacement.PalmistryV1,
|
||||||
});
|
});
|
||||||
const activeProduct = products[0]
|
const activeProduct = products[0]
|
||||||
|
|
||||||
const trialDuration = activeProduct?.trialDuration || 7;
|
const trialDuration = activeProduct?.trialDuration || 7;
|
||||||
|
|
||||||
const birthdate = useSelector(selectors.selectBirthdate);
|
const birthdate = useSelector(selectors.selectBirthdate);
|
||||||
@ -43,6 +49,28 @@ function TrialPayment() {
|
|||||||
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
|
const partnerZodiacSign = getZodiacSignByDate(partnerBirthdate);
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||||
|
|
||||||
|
const onPaymentSuccess = () => {
|
||||||
|
return navigate(routes.client.paymentSuccess())
|
||||||
|
}
|
||||||
|
|
||||||
|
const onModalClosed = () => {
|
||||||
|
setIsPaymentModalOpen(false);
|
||||||
|
return handleDiscount()
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDiscount = () => {
|
||||||
|
navigate(routes.client.palmistryV2SaveOff());
|
||||||
|
};
|
||||||
|
|
||||||
|
const onPaymentError = () => {
|
||||||
|
return navigate(routes.client.paymentFail())
|
||||||
|
}
|
||||||
|
|
||||||
|
const openPaymentModal = () => {
|
||||||
|
setIsPaymentModalOpen(true);
|
||||||
|
};
|
||||||
|
|
||||||
const singleOrWithPartner = useMemo(() => {
|
const singleOrWithPartner = useMemo(() => {
|
||||||
if (["relationship", "married"].includes(flowChoice)) {
|
if (["relationship", "married"].includes(flowChoice)) {
|
||||||
@ -51,14 +79,25 @@ function TrialPayment() {
|
|||||||
return "single";
|
return "single";
|
||||||
}, [flowChoice]);
|
}, [flowChoice]);
|
||||||
|
|
||||||
const handleNext = () => {
|
useEffect(() => {
|
||||||
navigate(routes.client.palmistryV2SaveOff());
|
if (!activeProduct) return;
|
||||||
};
|
dispatch(actions.payment.update({
|
||||||
|
activeProduct
|
||||||
|
}))
|
||||||
|
}, [activeProduct])
|
||||||
|
|
||||||
if (!activeProduct) return null;
|
if (!activeProduct) return null;
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<Modal containerClassName={styles.modal} open={isPaymentModalOpen} onClose={onModalClosed}>
|
||||||
|
<PaymentForm
|
||||||
|
placementKey={placementKey}
|
||||||
|
onPaymentError={onPaymentError}
|
||||||
|
onPaymentSuccess={onPaymentSuccess}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
<div className={styles.background} />
|
<div className={styles.background} />
|
||||||
<div className={styles.header}>
|
<div className={styles.header}>
|
||||||
<Title className={styles.title}>
|
<Title className={styles.title}>
|
||||||
@ -71,7 +110,7 @@ function TrialPayment() {
|
|||||||
</div>
|
</div>
|
||||||
<div className={styles.discount}>
|
<div className={styles.discount}>
|
||||||
<DiscountExpires />
|
<DiscountExpires />
|
||||||
<Button className={styles.button} onClick={handleNext}>
|
<Button className={styles.button} onClick={openPaymentModal}>
|
||||||
GET {trialDuration}-day trial
|
GET {trialDuration}-day trial
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
@ -82,8 +121,8 @@ function TrialPayment() {
|
|||||||
<PaymentTable
|
<PaymentTable
|
||||||
product={activeProduct}
|
product={activeProduct}
|
||||||
gender={gender}
|
gender={gender}
|
||||||
placementKey={EPlacementKeys["aura.placement.email.marketing"]}
|
placementKey={placementKey}
|
||||||
buttonClick={handleNext}
|
buttonClick={openPaymentModal}
|
||||||
/>
|
/>
|
||||||
<MoneyBackGuarantee />
|
<MoneyBackGuarantee />
|
||||||
<Title className={styles["title-hands"]}>
|
<Title className={styles["title-hands"]}>
|
||||||
|
|||||||
@ -33,7 +33,7 @@
|
|||||||
|
|
||||||
.discount {
|
.discount {
|
||||||
position: sticky;
|
position: sticky;
|
||||||
top: 0;
|
top: -1px;
|
||||||
width: calc(100% + 48px);
|
width: calc(100% + 48px);
|
||||||
padding: 9px 22px;
|
padding: 9px 22px;
|
||||||
border: solid 1px #fff;
|
border: solid 1px #fff;
|
||||||
@ -42,8 +42,9 @@
|
|||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
background-color: #dae7ff;
|
background-color: transparent;
|
||||||
z-index: 9999;
|
backdrop-filter: blur(5px);
|
||||||
|
z-index: 1000;
|
||||||
|
|
||||||
&>.button {
|
&>.button {
|
||||||
max-width: 176px;
|
max-width: 176px;
|
||||||
|
|||||||
134
src/components/Payment/nmi/CheckoutForm/index.tsx
Normal file
134
src/components/Payment/nmi/CheckoutForm/index.tsx
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
import MainButton from "@/components/MainButton";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
import { usePayment } from "@/hooks/payment/nmi/usePayment";
|
||||||
|
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
|
||||||
|
|
||||||
|
export type TConfirmType = "payment" | "setup";
|
||||||
|
|
||||||
|
interface ICheckoutFormProps {
|
||||||
|
subscriptionReceiptId?: string;
|
||||||
|
returnUrl?: string;
|
||||||
|
confirmType?: TConfirmType;
|
||||||
|
isHide?: boolean;
|
||||||
|
placementKey: EPlacementKeys;
|
||||||
|
activeProduct: IPaywallProduct;
|
||||||
|
onSuccess?: () => void;
|
||||||
|
onError?: (error?: string) => void;
|
||||||
|
onModalClosed?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function CheckoutForm({
|
||||||
|
placementKey,
|
||||||
|
activeProduct,
|
||||||
|
onError,
|
||||||
|
onSuccess,
|
||||||
|
onModalClosed,
|
||||||
|
isHide = false,
|
||||||
|
}: ICheckoutFormProps) {
|
||||||
|
|
||||||
|
const {
|
||||||
|
isLoading,
|
||||||
|
error,
|
||||||
|
isPaymentSuccess,
|
||||||
|
submitInlineForm,
|
||||||
|
formValidation,
|
||||||
|
isFormValid,
|
||||||
|
isModalClosed
|
||||||
|
} = usePayment({
|
||||||
|
placementKey,
|
||||||
|
activeProduct,
|
||||||
|
paymentFormType: "inline"
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (error && onError) {
|
||||||
|
console.log(error);
|
||||||
|
|
||||||
|
onError(error);
|
||||||
|
}
|
||||||
|
}, [error, onError]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isModalClosed && onModalClosed) {
|
||||||
|
onModalClosed();
|
||||||
|
}
|
||||||
|
}, [isModalClosed, onModalClosed]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isPaymentSuccess && onSuccess) {
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
}, [isPaymentSuccess, onSuccess]);
|
||||||
|
|
||||||
|
const handleSubmit = (e: React.FormEvent<HTMLFormElement> | React.MouseEvent<HTMLButtonElement>) => {
|
||||||
|
e.preventDefault();
|
||||||
|
if (!isFormValid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
submitInlineForm();
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<form
|
||||||
|
className={`${styles.form} ${isHide ? styles.hide : ""}`}
|
||||||
|
id="payment-form"
|
||||||
|
onSubmit={handleSubmit}
|
||||||
|
>
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<label htmlFor="card-number">Card Number</label>
|
||||||
|
<div
|
||||||
|
id="card-number"
|
||||||
|
className={`${styles.fieldContainer} ${formValidation.ccnumber.message ? styles.invalid : ''
|
||||||
|
} ${formValidation.ccnumber.isValid ? styles.valid : ''}`}
|
||||||
|
/>
|
||||||
|
{formValidation.ccnumber.message && (
|
||||||
|
<div className={styles.errorMessage}>
|
||||||
|
{formValidation.ccnumber.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formGroup}>
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<label htmlFor="card-expiry">Expiration Date</label>
|
||||||
|
<div
|
||||||
|
id="card-expiry"
|
||||||
|
className={`${styles.fieldContainer} ${formValidation.ccexp.message ? styles.invalid : ''
|
||||||
|
} ${formValidation.ccexp.isValid ? styles.valid : ''}`}
|
||||||
|
/>
|
||||||
|
{formValidation.ccexp.message && (
|
||||||
|
<div className={styles.errorMessage}>
|
||||||
|
{formValidation.ccexp.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className={styles.formRow}>
|
||||||
|
<label htmlFor="card-cvv">CVV</label>
|
||||||
|
<div
|
||||||
|
id="card-cvv"
|
||||||
|
className={`${styles.fieldContainer} ${formValidation.cvv.message ? styles.invalid : ''
|
||||||
|
} ${formValidation.cvv.isValid ? styles.valid : ''}`}
|
||||||
|
/>
|
||||||
|
{formValidation.cvv.message && (
|
||||||
|
<div className={styles.errorMessage}>
|
||||||
|
{formValidation.cvv.message}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<MainButton
|
||||||
|
color="blue"
|
||||||
|
disabled={isLoading || !isFormValid}
|
||||||
|
id="submit"
|
||||||
|
className={styles.button}
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
<img src="/lock.svg" alt="Secure" />
|
||||||
|
<span>{isLoading ? "Processing..." : "Pay Now"}</span>
|
||||||
|
</MainButton>
|
||||||
|
</form>
|
||||||
|
);
|
||||||
|
}
|
||||||
134
src/components/Payment/nmi/CheckoutForm/styles.module.scss
Normal file
134
src/components/Payment/nmi/CheckoutForm/styles.module.scss
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
.page {
|
||||||
|
/* position: relative; */
|
||||||
|
position: static;
|
||||||
|
/* height: calc(100vh - 50px);
|
||||||
|
max-height: -webkit-fill-available; */
|
||||||
|
display: flex;
|
||||||
|
justify-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
height: 0;
|
||||||
|
visibility: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-loader {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cross {
|
||||||
|
position: absolute;
|
||||||
|
top: -36px;
|
||||||
|
right: 28px;
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
cursor: pointer;
|
||||||
|
z-index: 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 27px;
|
||||||
|
font-weight: 700;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email {
|
||||||
|
font-size: 17px;
|
||||||
|
font-weight: 500;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button {
|
||||||
|
min-height: 0;
|
||||||
|
height: 50px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: #4caf50;
|
||||||
|
border-radius: 25px;
|
||||||
|
font-size: 16px;
|
||||||
|
gap: 8px;
|
||||||
|
opacity: 1;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
margin-top: 8px;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.7;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.button>img {
|
||||||
|
height: 18px;
|
||||||
|
margin-top: -5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 500px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.formRow {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
font-weight: 500;
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.formGroup {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.input {
|
||||||
|
width: 100%;
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 16px;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.fieldContainer {
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: border-color 0.2s;
|
||||||
|
padding: 8px;
|
||||||
|
min-height: 40px;
|
||||||
|
|
||||||
|
&:focus-within {
|
||||||
|
border-color: #3b82f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.invalid {
|
||||||
|
border-color: #dc3545;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.errorMessage {
|
||||||
|
color: #dc3545;
|
||||||
|
font-size: 12px;
|
||||||
|
margin: 4px 0;
|
||||||
|
min-height: 20px;
|
||||||
|
}
|
||||||
97
src/components/Payment/nmi/PaymentForm/index.tsx
Normal file
97
src/components/Payment/nmi/PaymentForm/index.tsx
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import { useTranslations } from "@/hooks/translations";
|
||||||
|
import styles from "./styles.module.scss";
|
||||||
|
import { addCurrency, ELocalesPlacement } from "@/locales";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { selectors } from "@/store";
|
||||||
|
import Loader from "@/components/Loader";
|
||||||
|
import Title from "@/components/Title";
|
||||||
|
import PaymentMethodsChoice from "@/components/pages/ABDesign/v1/pages/TrialPayment/components/PaymentMethodsChoice";
|
||||||
|
import { paymentMethods } from "@/data/paymentMethods";
|
||||||
|
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
|
||||||
|
import SecurityPayments from "@/components/pages/ABDesign/v1/pages/TrialPayment/components/SecurityPayments";
|
||||||
|
import CheckoutForm from "../CheckoutForm";
|
||||||
|
|
||||||
|
const paymentMethodsButtons = paymentMethods(null);
|
||||||
|
|
||||||
|
const getPrice = (product: IPaywallProduct | null) => {
|
||||||
|
if (!product) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (product.trialPrice === 100 ? 99 : product.trialPrice || 0) / 100;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IPaymentFormProps {
|
||||||
|
className?: string;
|
||||||
|
placementKey: EPlacementKeys;
|
||||||
|
onPaymentError?: () => void;
|
||||||
|
onPaymentSuccess?: () => void;
|
||||||
|
onModalClosed?: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
function PaymentForm({ className, placementKey, onPaymentError, onPaymentSuccess, onModalClosed }: IPaymentFormProps) {
|
||||||
|
const { translate } = useTranslations(ELocalesPlacement.V1);
|
||||||
|
const currency = useSelector(selectors.selectCurrency);
|
||||||
|
const activeProduct = useSelector(selectors.selectActiveProduct);
|
||||||
|
|
||||||
|
const isLoading = false;
|
||||||
|
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{isLoading && (
|
||||||
|
<div className={styles["payment-modal"]}>
|
||||||
|
<div className={styles["payment-loader"]}>
|
||||||
|
<Loader />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div
|
||||||
|
className={`${styles["payment-modal"]} ${isLoading ? styles.hide : ""} ${className}`}
|
||||||
|
>
|
||||||
|
<Title variant="h3" className={styles.title}>
|
||||||
|
{translate("payment_modal.title")}
|
||||||
|
</Title>
|
||||||
|
<PaymentMethodsChoice
|
||||||
|
paymentMethods={paymentMethodsButtons}
|
||||||
|
selectedPaymentMethod={paymentMethodsButtons[0].id}
|
||||||
|
onSelectPaymentMethod={() => { }}
|
||||||
|
/>
|
||||||
|
{activeProduct && (
|
||||||
|
<div>
|
||||||
|
<p className={styles["sub-plan-description"]}>
|
||||||
|
{translate("payment_modal.description", {
|
||||||
|
priceForDays: (
|
||||||
|
<b>
|
||||||
|
{translate("payment_modal.price_for_days", {
|
||||||
|
trialPrice: addCurrency(
|
||||||
|
getPrice(activeProduct),
|
||||||
|
currency
|
||||||
|
),
|
||||||
|
trialDuration: activeProduct?.trialDuration,
|
||||||
|
})}
|
||||||
|
</b>
|
||||||
|
),
|
||||||
|
emailReminder: (
|
||||||
|
<b>{translate("payment_modal.email_reminder")}</b>
|
||||||
|
),
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className={styles["payment-method-container"]}>
|
||||||
|
{!!activeProduct && <CheckoutForm
|
||||||
|
placementKey={placementKey}
|
||||||
|
activeProduct={activeProduct}
|
||||||
|
onError={onPaymentError}
|
||||||
|
onSuccess={onPaymentSuccess}
|
||||||
|
onModalClosed={onModalClosed}
|
||||||
|
/>}
|
||||||
|
</div>
|
||||||
|
<SecurityPayments />
|
||||||
|
<p className={styles.address}>{translate("payment_modal.address")}</p>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PaymentForm
|
||||||
55
src/components/Payment/nmi/PaymentForm/styles.module.scss
Normal file
55
src/components/Payment/nmi/PaymentForm/styles.module.scss
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
.payment-modal {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-height: 250px;
|
||||||
|
gap: 25px;
|
||||||
|
color: #2f2e37;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-modal.hide {
|
||||||
|
min-height: 0;
|
||||||
|
height: 0;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
text-align: center;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sub-plan-description {
|
||||||
|
font-size: 12px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 150%;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method-container {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.payment-method {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.address {
|
||||||
|
color: gray;
|
||||||
|
font-size: 10px;
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
// import { useNavigate } from "react-router-dom";
|
||||||
import routes from "@/routes";
|
// import routes from "@/routes";
|
||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import UserHeader from "../UserHeader";
|
import UserHeader from "../UserHeader";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
@ -16,7 +16,7 @@ import { getRandomArbitrary } from "@/services/random-value";
|
|||||||
|
|
||||||
function PriceListPage(): JSX.Element {
|
function PriceListPage(): JSX.Element {
|
||||||
const { translate } = useTranslations();
|
const { translate } = useTranslations();
|
||||||
const navigate = useNavigate();
|
// const navigate = useNavigate();
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const homeConfig = useSelector(selectors.selectHome);
|
const homeConfig = useSelector(selectors.selectHome);
|
||||||
const selectedPrice = useSelector(selectors.selectSelectedPrice);
|
const selectedPrice = useSelector(selectors.selectSelectedPrice);
|
||||||
@ -41,7 +41,7 @@ function PriceListPage(): JSX.Element {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
navigate(routes.client.subscription());
|
// navigate(routes.client.subscription());
|
||||||
}, 1000);
|
}, 1000);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -1,92 +0,0 @@
|
|||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
import { useTranslations } from "@/hooks/translations";
|
|
||||||
import Title from "../Title";
|
|
||||||
import routes from "@/routes";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
import ModalTop from "../ModalTop";
|
|
||||||
import Header from "../Header";
|
|
||||||
import { useCallback } from "react";
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { actions, selectors } from "@/store";
|
|
||||||
import MainButton from "../MainButton";
|
|
||||||
|
|
||||||
interface ModalTopProps {
|
|
||||||
open: boolean;
|
|
||||||
onClose?: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SpecialWelcomeOffer({ open, onClose }: ModalTopProps): JSX.Element {
|
|
||||||
const { translate } = useTranslations();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
const selectedPrice = useSelector(selectors.selectSelectedPrice);
|
|
||||||
const halfPrice = (Math.round(selectedPrice || 0) / 2).toFixed(2);
|
|
||||||
const updateIsDiscount = useCallback((isDiscount: boolean) => {
|
|
||||||
dispatch(
|
|
||||||
actions.payment.update({
|
|
||||||
isDiscount
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}, [dispatch]);
|
|
||||||
const handleNext = () => {
|
|
||||||
updateIsDiscount(true);
|
|
||||||
navigate(routes.client.paymentMethod());
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleMoreAbout = () => {
|
|
||||||
window.location.href = "https://witapps.us/en/aura";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{open ? (
|
|
||||||
<ModalTop open={open} onClose={onClose || handleNext}>
|
|
||||||
<Header
|
|
||||||
showBack={false}
|
|
||||||
showCross={false}
|
|
||||||
clickCross={onClose || handleNext}
|
|
||||||
/>
|
|
||||||
<div className={styles.content}>
|
|
||||||
{/* <span className={styles['welcome-offer']}>{translate('special_welcome_offer')}</span> */}
|
|
||||||
<img src="/your-friends.webp" alt="Your friends" />
|
|
||||||
<Title variant="h2" className={styles["your-friends"]}>
|
|
||||||
{translate("au.friends.window")}
|
|
||||||
</Title>
|
|
||||||
<Title variant="h2" className={styles["get-50-only"]}>
|
|
||||||
{translate("au.get50.only")}
|
|
||||||
</Title>
|
|
||||||
<div className={styles["discount-container"]}>
|
|
||||||
{Number(halfPrice) > 0 &&
|
|
||||||
<>
|
|
||||||
<span className={styles["red-price"]}>${selectedPrice}</span>{" "}
|
|
||||||
–<span className={styles["price"]}>
|
|
||||||
{/* ${halfPrice} */}
|
|
||||||
$1
|
|
||||||
</span>
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
{!Number(halfPrice) && <span className={styles["free-trial"]}>{translate('au.free_trial_web.7_14')}</span>}
|
|
||||||
</div>
|
|
||||||
<MainButton
|
|
||||||
className={styles["button-green"]}
|
|
||||||
onClick={handleNext}
|
|
||||||
>
|
|
||||||
{/* $ {halfPrice} – */}
|
|
||||||
{translate("au.try_for.button")}
|
|
||||||
</MainButton>
|
|
||||||
<MainButton
|
|
||||||
// disabled
|
|
||||||
className={styles["button-black"]}
|
|
||||||
onClick={handleMoreAbout}
|
|
||||||
>
|
|
||||||
<img className={styles["button-icon"]} src="/leo.webp" alt="Leo" />
|
|
||||||
{translate("au.more_llc.button")}
|
|
||||||
</MainButton>
|
|
||||||
</div>
|
|
||||||
</ModalTop>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SpecialWelcomeOffer;
|
|
||||||
@ -1,73 +0,0 @@
|
|||||||
.content {
|
|
||||||
padding: 0 24px 64px 24px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding-top: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.welcome-offer {
|
|
||||||
color: #717171;
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discount-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 16px;
|
|
||||||
font-size: 32px;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.red-price {
|
|
||||||
color: red;
|
|
||||||
text-decoration: line-through;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-green {
|
|
||||||
background-color: #18d136;
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 21px;
|
|
||||||
/* color: #fff;
|
|
||||||
border-radius: 26px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
padding: 12px 0;
|
|
||||||
border: none;
|
|
||||||
font-size: 14px;
|
|
||||||
margin-top: 16px; */
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-black {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 21px;
|
|
||||||
position: relative;
|
|
||||||
}
|
|
||||||
|
|
||||||
.your-friends {
|
|
||||||
width: 80%;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.get-50-only {
|
|
||||||
margin-bottom: 0;
|
|
||||||
font-weight: 700;
|
|
||||||
color: #ff003d;
|
|
||||||
}
|
|
||||||
|
|
||||||
.button-icon {
|
|
||||||
position: absolute;
|
|
||||||
width: 48px;
|
|
||||||
top: 50%;
|
|
||||||
left: 13px;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
.free-trial {
|
|
||||||
font-size: 22px;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
@ -1,36 +0,0 @@
|
|||||||
import { useParams } from "react-router";
|
|
||||||
import { useTranslations } from "@/hooks/translations";
|
|
||||||
import { useApi, useApiCall, Element } from "@/api";
|
|
||||||
import { useCallback } from "react";
|
|
||||||
import parse from "html-react-parser";
|
|
||||||
import Loader from "../Loader";
|
|
||||||
import NotFoundPage from "../NotFoundPage";
|
|
||||||
import "./styles.css";
|
|
||||||
|
|
||||||
function StaticPage(): JSX.Element {
|
|
||||||
const { i18n } = useTranslations();
|
|
||||||
const { typeId } = useParams();
|
|
||||||
const api = useApi();
|
|
||||||
const locale = i18n.language;
|
|
||||||
const loadData = useCallback(() => {
|
|
||||||
const type = typeId || "";
|
|
||||||
return api
|
|
||||||
.getElement({ type, locale })
|
|
||||||
.then((resp: Element.Response) => resp.data.element);
|
|
||||||
}, [api, typeId, locale]);
|
|
||||||
const { data, isPending, error } = useApiCall<Element.Element>(loadData);
|
|
||||||
const content = data ? parse(data.body) : null;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<section className="page page-static">
|
|
||||||
{isPending ? (
|
|
||||||
<Loader />
|
|
||||||
) : (
|
|
||||||
<div className="page-static__content">{content}</div>
|
|
||||||
)}
|
|
||||||
{error && <NotFoundPage />}
|
|
||||||
</section>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default StaticPage;
|
|
||||||
@ -1,18 +0,0 @@
|
|||||||
.page-static {
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-static p {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-static h1,
|
|
||||||
.page-static h2,
|
|
||||||
.page-static h3 {
|
|
||||||
margin-top: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-static__content {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
@ -1,272 +0,0 @@
|
|||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { useTranslations } from "@/hooks/translations";
|
|
||||||
import { useNavigate, useParams } from "react-router-dom";
|
|
||||||
import { actions, selectors } from "@/store";
|
|
||||||
import MainButton from "../MainButton";
|
|
||||||
import Policy from "../Policy";
|
|
||||||
import PaymentTable, { Currency, Locale } from "../PaymentTable";
|
|
||||||
import CallToAction from "../CallToAction";
|
|
||||||
import routes from "@/routes";
|
|
||||||
import styles from "./styles.module.css";
|
|
||||||
// import Header from "../Header";
|
|
||||||
// import SpecialWelcomeOffer from "../SpecialWelcomeOffer";
|
|
||||||
import { useEffect, useState } from "react";
|
|
||||||
import { ApiError, extractErrorMessage, useApi } from "@/api";
|
|
||||||
import { useAuth } from "@/auth";
|
|
||||||
import { getClientTimezone, language } from "@/locales";
|
|
||||||
import Loader from "../Loader";
|
|
||||||
import Title from "../Title";
|
|
||||||
import ErrorText from "../ErrorText";
|
|
||||||
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
|
|
||||||
import { usePaywall } from "@/hooks/paywall/usePaywall";
|
|
||||||
|
|
||||||
const currency = Currency.USD;
|
|
||||||
const locale = language as Locale;
|
|
||||||
|
|
||||||
const getPrice = (product: IPaywallProduct | null) => {
|
|
||||||
if (!product?.trialPrice) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (product.trialPrice === 100 ? 99 : product.trialPrice || 0) / 100;
|
|
||||||
};
|
|
||||||
|
|
||||||
function SubscriptionPage(): JSX.Element {
|
|
||||||
const api = useApi();
|
|
||||||
const timezone = getClientTimezone();
|
|
||||||
const { signUp } = useAuth();
|
|
||||||
const { translate } = useTranslations();
|
|
||||||
const navigate = useNavigate();
|
|
||||||
const dispatch = useDispatch();
|
|
||||||
// const [isOpenModal, setIsOpenModal] = useState(false);
|
|
||||||
const [email, setEmail] = useState("");
|
|
||||||
const [emailError, setEmailError] = useState<null | string>(
|
|
||||||
"Email is invalid"
|
|
||||||
);
|
|
||||||
const [name, setName] = useState("");
|
|
||||||
const [nameError, setNameError] = useState<null | string>("Name is invalid");
|
|
||||||
const [isSubmit, setIsSubmit] = useState(false);
|
|
||||||
const [isAuth, setIsAuth] = useState(false);
|
|
||||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
|
||||||
const [apiError, setApiError] = useState<ApiError | null>(null);
|
|
||||||
const [error, setError] = useState<boolean>(false);
|
|
||||||
const { subPlan } = useParams();
|
|
||||||
const birthday = useSelector(selectors.selectBirthday);
|
|
||||||
console.log(nameError);
|
|
||||||
|
|
||||||
const { products } = usePaywall({
|
|
||||||
placementKey: EPlacementKeys["aura.placement.main"],
|
|
||||||
});
|
|
||||||
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
|
|
||||||
const [activeProduct, setActiveProduct] = useState<IPaywallProduct | null>(
|
|
||||||
activeProductFromStore
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (subPlan) {
|
|
||||||
const targetProduct = products.find(
|
|
||||||
(product) =>
|
|
||||||
String(
|
|
||||||
product?.trialPrice
|
|
||||||
? Math.floor((product?.trialPrice + 1) / 100)
|
|
||||||
: product.key.replace(".", "")
|
|
||||||
) === subPlan
|
|
||||||
);
|
|
||||||
if (targetProduct) {
|
|
||||||
setActiveProduct(targetProduct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [products, subPlan]);
|
|
||||||
|
|
||||||
const paymentItems = [
|
|
||||||
{
|
|
||||||
title: activeProduct?.name || "Per 7-Day Trial For",
|
|
||||||
price: getPrice(activeProduct),
|
|
||||||
description: activeProduct?.description?.length
|
|
||||||
? activeProduct.description
|
|
||||||
: translate("au.2week_plan.web"),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const authorization = async () => {
|
|
||||||
try {
|
|
||||||
setIsLoading(true);
|
|
||||||
const auth = await api.auth({ email, timezone, locale });
|
|
||||||
const {
|
|
||||||
auth: { token, user },
|
|
||||||
} = auth;
|
|
||||||
signUp(token, user);
|
|
||||||
const payload = {
|
|
||||||
user: { profile_attributes: { birthday } },
|
|
||||||
token,
|
|
||||||
};
|
|
||||||
const updatedUser = await api.updateUser(payload).catch((error) => {
|
|
||||||
console.log("Error: ", error);
|
|
||||||
});
|
|
||||||
if (updatedUser?.user) {
|
|
||||||
dispatch(actions.user.update(updatedUser.user));
|
|
||||||
}
|
|
||||||
if (name) {
|
|
||||||
dispatch(
|
|
||||||
actions.user.update({
|
|
||||||
username: name,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
|
||||||
dispatch(actions.status.update("registred"));
|
|
||||||
dispatch(
|
|
||||||
actions.payment.update({
|
|
||||||
activeProduct,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
setIsLoading(false);
|
|
||||||
setIsAuth(true);
|
|
||||||
setTimeout(() => {
|
|
||||||
navigate(routes.client.paymentMethod());
|
|
||||||
}, 1000);
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
if (error instanceof ApiError) {
|
|
||||||
setApiError(error as ApiError);
|
|
||||||
} else {
|
|
||||||
setError(true);
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClick = async () => {
|
|
||||||
setIsSubmit(true);
|
|
||||||
|
|
||||||
if (
|
|
||||||
!isValidEmail(email)
|
|
||||||
// || !isValidName(name)
|
|
||||||
) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await authorization();
|
|
||||||
};
|
|
||||||
// const handleCross = () => setIsOpenModal(true);
|
|
||||||
const policyLink = (
|
|
||||||
<a href="https://aura.wit.life/" target="_blank" rel="noopener noreferrer">
|
|
||||||
{translate("subscription_policy")}
|
|
||||||
</a>
|
|
||||||
);
|
|
||||||
|
|
||||||
const isValidEmail = (email: string) => {
|
|
||||||
return /\S+@\S+\.\S+/.test(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const isValidName = (name: string) => {
|
|
||||||
return !!(name.length > 0 && name.length < 30);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const email = event.target.value;
|
|
||||||
if (!isValidEmail(email)) {
|
|
||||||
setEmailError("Email is invalid");
|
|
||||||
} else {
|
|
||||||
setEmailError(null);
|
|
||||||
}
|
|
||||||
setEmail(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChangeName = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
||||||
const name = event.target.value;
|
|
||||||
if (!isValidName(name)) {
|
|
||||||
setNameError("Name is invalid");
|
|
||||||
} else {
|
|
||||||
setNameError(null);
|
|
||||||
}
|
|
||||||
setName(name);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{/* <SpecialWelcomeOffer open={isOpenModal} onClose={handleClick} /> */}
|
|
||||||
{/* <Header classCross={styles.cross} clickCross={handleCross} /> */}
|
|
||||||
{/* <UserHeader email={email} /> */}
|
|
||||||
<section className={`${styles.page} page`}>
|
|
||||||
<CallToAction />
|
|
||||||
{/* <Countdown start={10} /> */}
|
|
||||||
<PaymentTable
|
|
||||||
items={paymentItems}
|
|
||||||
currency={currency}
|
|
||||||
locale={locale}
|
|
||||||
/>
|
|
||||||
<div className={styles["inputs-container"]}>
|
|
||||||
<div className={styles["inputs-container__input-container"]}>
|
|
||||||
<input
|
|
||||||
className={`${styles["inputs-container__input"]}`}
|
|
||||||
// ${
|
|
||||||
// nameError && isSubmit && styles["inputs-container__input-error"]
|
|
||||||
// }
|
|
||||||
type="name"
|
|
||||||
name="name"
|
|
||||||
id="name"
|
|
||||||
value={name}
|
|
||||||
onChange={handleChangeName}
|
|
||||||
placeholder="Your name"
|
|
||||||
/>
|
|
||||||
{/* {isSubmit && !!nameError && (
|
|
||||||
<span className={styles["inputs-container__label-error"]}>
|
|
||||||
{nameError}
|
|
||||||
</span>
|
|
||||||
)} */}
|
|
||||||
</div>
|
|
||||||
<div className={styles["inputs-container__input-container"]}>
|
|
||||||
<input
|
|
||||||
className={`${styles["inputs-container__input"]} ${
|
|
||||||
emailError &&
|
|
||||||
isSubmit &&
|
|
||||||
styles["inputs-container__input-error"]
|
|
||||||
}`}
|
|
||||||
type="email"
|
|
||||||
name="email"
|
|
||||||
id="email"
|
|
||||||
value={email}
|
|
||||||
onChange={handleChangeEmail}
|
|
||||||
placeholder="Your email"
|
|
||||||
/>
|
|
||||||
{isSubmit && !!emailError && (
|
|
||||||
<span className={styles["inputs-container__label-error"]}>
|
|
||||||
{emailError}
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
{isLoading && isSubmit && isValidEmail(email) && (
|
|
||||||
// isValidName(name) &&
|
|
||||||
<Loader />
|
|
||||||
)}
|
|
||||||
{(error || apiError) && (
|
|
||||||
<Title variant="h3" style={{ color: "red", margin: 0 }}>
|
|
||||||
Something went wrong
|
|
||||||
</Title>
|
|
||||||
)}
|
|
||||||
{apiError && (
|
|
||||||
<ErrorText
|
|
||||||
size="medium"
|
|
||||||
isShown={Boolean(apiError)}
|
|
||||||
message={apiError ? extractErrorMessage(apiError) : null}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{!apiError && !error && !isLoading && isAuth && (
|
|
||||||
<img src="/SuccessIcon.webp" alt="Success Icon" />
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className={styles["subscription-action"]}>
|
|
||||||
<MainButton onClick={handleClick}>
|
|
||||||
Start ${getPrice(activeProduct || null)}
|
|
||||||
</MainButton>
|
|
||||||
</div>
|
|
||||||
<Policy>
|
|
||||||
<>
|
|
||||||
{translate("auweb.agree.text1")}
|
|
||||||
{translate("subscription_text", { policyLink })}
|
|
||||||
</>
|
|
||||||
</Policy>
|
|
||||||
</section>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default SubscriptionPage;
|
|
||||||
@ -1,59 +0,0 @@
|
|||||||
.page {
|
|
||||||
padding-bottom: 32px !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.subscription-action {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
right: 0;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
background-color: transparent;
|
|
||||||
padding: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cross {
|
|
||||||
left: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container {
|
|
||||||
width: 100%;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 16px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container__input {
|
|
||||||
width: 100%;
|
|
||||||
border: solid #000 2px;
|
|
||||||
border-radius: 20px;
|
|
||||||
font-size: 17px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
padding: 16px 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container__input-container {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container__input-error {
|
|
||||||
border-color: red;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inputs-container__label-error {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 400;
|
|
||||||
color: red;
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 25px;
|
|
||||||
}
|
|
||||||
@ -26,119 +26,9 @@ import { useTranslations } from "@/hooks/translations";
|
|||||||
import {
|
import {
|
||||||
addCurrency,
|
addCurrency,
|
||||||
ELocalesPlacement,
|
ELocalesPlacement,
|
||||||
getDefaultLocaleByLanguage,
|
|
||||||
language,
|
|
||||||
} from "@/locales";
|
} from "@/locales";
|
||||||
import DiscountExpires from "../TrialPayment/components/DiscountExpires";
|
import DiscountExpires from "../TrialPayment/components/DiscountExpires";
|
||||||
|
|
||||||
const textVariants = [
|
|
||||||
<>
|
|
||||||
AURA is the only accurate app with reliable astrological predictions,
|
|
||||||
verified by professionals and guaranteed to provide accurate astrological
|
|
||||||
forecasts.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
AURA has already helped millions of people find happiness and discover the
|
|
||||||
whole truth about their relationships.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
An astrological forecast that will completely change your life is almost
|
|
||||||
ready! Before we provide it to you, we would like to offer you the
|
|
||||||
opportunity to choose the amount you consider reasonable to try AURA for 7
|
|
||||||
days and which you think is fair for the changes that will happen to you:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
- You will discover all the most intimate secrets that the stars have
|
|
||||||
prepared for you and solve relationship issues within just one month;
|
|
||||||
<br />
|
|
||||||
- You will once and for all put the finishing touches on unresolved issues
|
|
||||||
and forget about problems that have been haunting you for years (if not
|
|
||||||
decades);
|
|
||||||
<br />
|
|
||||||
- You will save hundreds of dollars on fake and unprofessional astrological
|
|
||||||
predictions and fortune tellers;
|
|
||||||
<br />
|
|
||||||
- You will receive not only a personal astrological forecast but also
|
|
||||||
personalized daily horoscopes, learn who and how is draining your energy,
|
|
||||||
and get other personalized readings.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
|
||||||
A 7-day trial period costs us $5, but please choose the amount that suits
|
|
||||||
you best:
|
|
||||||
</span>
|
|
||||||
</>,
|
|
||||||
<>
|
|
||||||
AURA is the only app you can trust for accurate astrological insights,
|
|
||||||
crafted and verified by seasoned professionals, ensuring you receive
|
|
||||||
predictions that are both reliable and transformative.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
AURA has already transformed the lives of millions, bringing clarity, joy,
|
|
||||||
and a deeper understanding of their relationships.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Your life-changing astrological forecast is almost ready! But before we
|
|
||||||
reveal the secrets that will change your life, we want to give you the
|
|
||||||
freedom to choose how much you feel is fair to try AURA for 7 days. This is
|
|
||||||
your chance to decide what the transformation is worth to you:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
- Uncover the deepest, most intimate secrets the stars have in store for
|
|
||||||
you, and watch your relationship issues resolve in just one month;
|
|
||||||
<br />
|
|
||||||
- Finally, put an end to those lingering issues that have been troubling you
|
|
||||||
for years, maybe even decades;
|
|
||||||
<br />
|
|
||||||
- Save hundreds of dollars by avoiding unreliable astrologers and fake
|
|
||||||
fortune tellers;
|
|
||||||
<br />
|
|
||||||
- Receive not just a personal astrological forecast but also personalized
|
|
||||||
daily horoscopes, learn who's draining your energy, and get exclusive,
|
|
||||||
tailored readings.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
|
||||||
While a 7-day trial costs us $5, we want you to choose the amount you
|
|
||||||
believe is right for you:
|
|
||||||
</span>
|
|
||||||
</>,
|
|
||||||
<>
|
|
||||||
Discover AURA—the only app that delivers truly accurate astrological
|
|
||||||
forecasts, with predictions you can trust, all verified by top industry
|
|
||||||
professionals. Your path to clarity and happiness starts here.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Millions have already found happiness and uncovered the truth about their
|
|
||||||
relationships with AURA. Now, it’s your turn.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
Your life-changing astrological forecast is almost ready! Before we share
|
|
||||||
this powerful insight with you, we’re giving you the chance to set your own
|
|
||||||
price to experience AURA for 7 days. You decide what feels right for the
|
|
||||||
life-changing revelations you’ll receive:
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
- Reveal the deepest secrets the universe has in store for you and resolve
|
|
||||||
your relationship dilemmas within a month;
|
|
||||||
<br />
|
|
||||||
- Finally close the chapter on long-standing issues that have plagued you
|
|
||||||
for years, perhaps even decades;
|
|
||||||
<br />
|
|
||||||
- Avoid wasting hundreds of dollars on untrustworthy, fake astrologers;
|
|
||||||
<br />
|
|
||||||
- Gain access to your personal astrological forecast, receive daily
|
|
||||||
personalized horoscopes, learn who’s been draining your energy, and benefit
|
|
||||||
from other insightful readings.
|
|
||||||
<br />
|
|
||||||
<br />
|
|
||||||
<span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
|
||||||
Our 7-day trial typically costs us $5, but you get to choose the amount
|
|
||||||
that feels right for you:
|
|
||||||
</span>
|
|
||||||
</>,
|
|
||||||
];
|
|
||||||
|
|
||||||
enum EDisplayOptionButton {
|
enum EDisplayOptionButton {
|
||||||
"alwaysVisible" = "alwaysVisible",
|
"alwaysVisible" = "alwaysVisible",
|
||||||
"visibleIfChosen" = "visibleIfChosen",
|
"visibleIfChosen" = "visibleIfChosen",
|
||||||
@ -168,7 +58,38 @@ function TrialChoicePage() {
|
|||||||
|
|
||||||
const { flags } = useMetricABFlags();
|
const { flags } = useMetricABFlags();
|
||||||
const isShowTimer = flags?.showTimerTrial?.[0] === "show";
|
const isShowTimer = flags?.showTimerTrial?.[0] === "show";
|
||||||
const textVariant = Number(flags?.text?.[0]) || 0;
|
|
||||||
|
const textVariants = [
|
||||||
|
translate("/trial-choice.text_variant_1", {
|
||||||
|
br: "\n",
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
span: <span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
||||||
|
{translate("/trial-choice.text_variant_1_span", {
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
})}
|
||||||
|
</span>,
|
||||||
|
}),
|
||||||
|
translate("/trial-choice.text_variant_2", {
|
||||||
|
br: "\n",
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
span: <span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
||||||
|
{translate("/trial-choice.text_variant_2_span", {
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
})}
|
||||||
|
</span>,
|
||||||
|
}),
|
||||||
|
translate("/trial-choice.text_variant_3", {
|
||||||
|
br: "\n",
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
span: <span className={`${styles.text} ${styles.bold} ${styles.blue}`}>
|
||||||
|
{translate("/trial-choice.text_variant_3_span", {
|
||||||
|
trialDuration: activeProduct?.trialDuration?.toString() || "7",
|
||||||
|
})}
|
||||||
|
</span>,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
|
||||||
|
const textVariant = Number(flags?.text?.[0]) || 2;
|
||||||
|
|
||||||
const getPrice = useCallback(
|
const getPrice = useCallback(
|
||||||
(product: IPaywallProduct) => {
|
(product: IPaywallProduct) => {
|
||||||
@ -276,7 +197,7 @@ function TrialChoicePage() {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{(!textVariant || getDefaultLocaleByLanguage(language) !== "en") && (
|
{(!textVariant && (
|
||||||
<>
|
<>
|
||||||
<p
|
<p
|
||||||
className={styles.text}
|
className={styles.text}
|
||||||
@ -315,14 +236,14 @@ function TrialChoicePage() {
|
|||||||
})}
|
})}
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)}
|
))}
|
||||||
|
{!!textVariant && (
|
||||||
{!!textVariant && getDefaultLocaleByLanguage(language) === "en" && (
|
|
||||||
<p
|
<p
|
||||||
className={styles.text}
|
className={styles.text}
|
||||||
style={{
|
style={{
|
||||||
marginTop: getFirstParagraphMargin(),
|
marginTop: getFirstParagraphMargin(),
|
||||||
textAlign: "left",
|
textAlign: "left",
|
||||||
|
whiteSpace: "pre-line",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{textVariants[textVariant - 1]}
|
{textVariants[textVariant - 1]}
|
||||||
@ -357,8 +278,8 @@ function TrialChoicePage() {
|
|||||||
style={
|
style={
|
||||||
arrowLeft
|
arrowLeft
|
||||||
? {
|
? {
|
||||||
left: arrowLeft,
|
left: arrowLeft,
|
||||||
}
|
}
|
||||||
: {}
|
: {}
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
@ -402,9 +323,8 @@ function TrialChoicePage() {
|
|||||||
isActiveBlur={true}
|
isActiveBlur={true}
|
||||||
>
|
>
|
||||||
<QuestionnaireGreenButton
|
<QuestionnaireGreenButton
|
||||||
className={`${styles.button} ${
|
className={`${styles.button} ${isDisabled ? styles.disabled : ""
|
||||||
isDisabled ? styles.disabled : ""
|
}`}
|
||||||
}`}
|
|
||||||
onClick={handleNext}
|
onClick={handleNext}
|
||||||
>
|
>
|
||||||
{getText("text.button.1", {
|
{getText("text.button.1", {
|
||||||
|
|||||||
@ -28,8 +28,8 @@ import metricService, {
|
|||||||
} from "@/services/metric/metricService";
|
} from "@/services/metric/metricService";
|
||||||
import { useTranslations } from "@/hooks/translations";
|
import { useTranslations } from "@/hooks/translations";
|
||||||
import { ELocalesPlacement } from "@/locales";
|
import { ELocalesPlacement } from "@/locales";
|
||||||
import { usePayment } from "@/hooks/payment/nmi/usePayment";
|
import Modal from "@/components/Modal";
|
||||||
import Loader, { LoaderColor } from "@/components/Loader";
|
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
|
||||||
|
|
||||||
const placementKey = EPlacementKeys["aura.placement.redesign.main"]
|
const placementKey = EPlacementKeys["aura.placement.redesign.main"]
|
||||||
|
|
||||||
@ -60,28 +60,20 @@ function TrialPaymentPage() {
|
|||||||
>("single");
|
>("single");
|
||||||
const { subPlan } = useParams();
|
const { subPlan } = useParams();
|
||||||
|
|
||||||
const { isLoading, isModalClosed, error, isPaymentSuccess, showCreditCardForm } = usePayment({
|
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||||
placementKey,
|
|
||||||
activeProduct: activeProduct!
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
const onPaymentSuccess = () => {
|
||||||
if (isPaymentSuccess) {
|
return navigate(routes.client.paymentSuccess())
|
||||||
return navigate(routes.client.paymentSuccess())
|
}
|
||||||
}
|
|
||||||
}, [isPaymentSuccess])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const onModalClosed = () => {
|
||||||
if (isModalClosed && !isPaymentSuccess && !isLoading) {
|
setIsPaymentModalOpen(false);
|
||||||
return handleDiscount()
|
return handleDiscount()
|
||||||
}
|
}
|
||||||
}, [isModalClosed, isPaymentSuccess, isLoading])
|
|
||||||
|
|
||||||
useEffect(() => {
|
const onPaymentError = () => {
|
||||||
if (error?.length && error !== "Product not found") {
|
return navigate(routes.client.paymentFail())
|
||||||
return navigate(routes.client.paymentFail())
|
}
|
||||||
}
|
|
||||||
}, [error])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
metricService.reachGoal(EGoals.AURA_TRIAL_PAYMENT_PAGE_VISIT, [
|
metricService.reachGoal(EGoals.AURA_TRIAL_PAYMENT_PAGE_VISIT, [
|
||||||
@ -137,7 +129,7 @@ function TrialPaymentPage() {
|
|||||||
|
|
||||||
const openPaymentModal = () => {
|
const openPaymentModal = () => {
|
||||||
metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED);
|
metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED);
|
||||||
showCreditCardForm()
|
setIsPaymentModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -148,11 +140,13 @@ function TrialPaymentPage() {
|
|||||||
backgroundColor: gender === "male" ? "#C1E5FF" : "#F7EBFF",
|
backgroundColor: gender === "male" ? "#C1E5FF" : "#F7EBFF",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{isLoading &&
|
<Modal containerClassName={styles.modal} open={isPaymentModalOpen} onClose={onModalClosed}>
|
||||||
<div className={styles["loader-container"]}>
|
<PaymentForm
|
||||||
<Loader color={LoaderColor.White} />
|
placementKey={placementKey}
|
||||||
</div>
|
onPaymentError={onPaymentError}
|
||||||
}
|
onPaymentSuccess={onPaymentSuccess}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
<div className={styles["background-top-blob-container"]}>
|
<div className={styles["background-top-blob-container"]}>
|
||||||
<BackgroundTopBlob
|
<BackgroundTopBlob
|
||||||
width={pageWidth}
|
width={pageWidth}
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import styles from "./styles.module.css";
|
import styles from "./styles.module.css";
|
||||||
import PaymentDiscountTable from "./PaymentDiscountTable";
|
import PaymentDiscountTable from "./PaymentDiscountTable";
|
||||||
import { useEffect } from "react";
|
import { useState } from "react";
|
||||||
import { selectors } from "@/store";
|
import { selectors } from "@/store";
|
||||||
import { useSelector } from "react-redux";
|
import { useSelector } from "react-redux";
|
||||||
import { EPlacementKeys } from "@/api/resources/Paywall";
|
import { EPlacementKeys } from "@/api/resources/Paywall";
|
||||||
@ -8,52 +8,49 @@ import { useTranslations } from "@/hooks/translations";
|
|||||||
import { addCurrency, ELocalesPlacement } from "@/locales";
|
import { addCurrency, ELocalesPlacement } from "@/locales";
|
||||||
import DiscountLayout from "../../layouts/Discount/DiscountLayout";
|
import DiscountLayout from "../../layouts/Discount/DiscountLayout";
|
||||||
import QuestionnaireGreenButton from "../../ui/GreenButton";
|
import QuestionnaireGreenButton from "../../ui/GreenButton";
|
||||||
import { usePayment } from "@/hooks/payment/nmi/usePayment";
|
|
||||||
import { Navigate, useNavigate } from "react-router-dom";
|
import { Navigate, useNavigate } from "react-router-dom";
|
||||||
import routes from "@/routes";
|
import routes from "@/routes";
|
||||||
import Loader, { LoaderColor } from "@/components/Loader";
|
import PaymentForm from "@/components/Payment/nmi/PaymentForm";
|
||||||
|
import Modal from "@/components/Modal";
|
||||||
|
|
||||||
|
const placementKey = EPlacementKeys["aura.placement.secret.discount"];
|
||||||
|
|
||||||
function TrialPaymentWithDiscount() {
|
function TrialPaymentWithDiscount() {
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const { translate } = useTranslations(ELocalesPlacement.V1);
|
const { translate } = useTranslations(ELocalesPlacement.V1);
|
||||||
const activeProduct = useSelector(selectors.selectActiveProduct);
|
const activeProduct = useSelector(selectors.selectActiveProduct);
|
||||||
const currency = useSelector(selectors.selectCurrency);
|
const currency = useSelector(selectors.selectCurrency);
|
||||||
|
const [isPaymentModalOpen, setIsPaymentModalOpen] = useState(false);
|
||||||
|
|
||||||
if (!activeProduct) {
|
if (!activeProduct) {
|
||||||
return <Navigate to={routes.client.additionalDiscountV1()} />
|
return <Navigate to={routes.client.additionalDiscountV1()} />
|
||||||
}
|
}
|
||||||
|
|
||||||
const { isLoading, error, isPaymentSuccess, showCreditCardForm } = usePayment({
|
const onPaymentSuccess = () => {
|
||||||
placementKey: EPlacementKeys["aura.placement.secret.discount"],
|
return navigate(routes.client.paymentSuccess())
|
||||||
activeProduct: activeProduct!
|
}
|
||||||
});
|
|
||||||
|
|
||||||
|
const onModalClosed = () => {
|
||||||
|
setIsPaymentModalOpen(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
const onPaymentError = () => {
|
||||||
useEffect(() => {
|
return navigate(routes.client.paymentFail())
|
||||||
if (isPaymentSuccess) {
|
}
|
||||||
return navigate(routes.client.paymentSuccess())
|
|
||||||
}
|
|
||||||
}, [isPaymentSuccess])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (error?.length && error !== "Product not found") {
|
|
||||||
return navigate(routes.client.paymentFail())
|
|
||||||
}
|
|
||||||
}, [error])
|
|
||||||
|
|
||||||
|
|
||||||
const openPaymentModal = () => {
|
const openPaymentModal = () => {
|
||||||
showCreditCardForm()
|
setIsPaymentModalOpen(true);
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DiscountLayout title={translate("/trial-payment-with-discount.title")}>
|
<DiscountLayout title={translate("/trial-payment-with-discount.title")}>
|
||||||
{isLoading &&
|
<Modal containerClassName={styles.modal} open={isPaymentModalOpen} onClose={onModalClosed}>
|
||||||
<div className={styles["loader-container"]}>
|
<PaymentForm
|
||||||
<Loader color={LoaderColor.White} />
|
placementKey={placementKey}
|
||||||
</div>
|
onPaymentError={onPaymentError}
|
||||||
}
|
onPaymentSuccess={onPaymentSuccess}
|
||||||
|
/>
|
||||||
|
</Modal>
|
||||||
<PaymentDiscountTable />
|
<PaymentDiscountTable />
|
||||||
|
|
||||||
<div className={styles['button-wrapper']}>
|
<div className={styles['button-wrapper']}>
|
||||||
|
|||||||
@ -43,18 +43,6 @@ export default function DiscountScreen() {
|
|||||||
navigate(routes.client.palmistryPremiumBundle());
|
navigate(routes.client.palmistryPremiumBundle());
|
||||||
};
|
};
|
||||||
|
|
||||||
// React.useEffect(() => {
|
|
||||||
// (async () => {
|
|
||||||
// const { sub_plans } = await api.getSubscriptionPlans({ locale });
|
|
||||||
// const plan = sub_plans.find((plan) => plan.id === "stripe.40");
|
|
||||||
//
|
|
||||||
// if (!plan?.price_cents) return;
|
|
||||||
//
|
|
||||||
// setPrice((plan?.price_cents / 100).toFixed(2));
|
|
||||||
// })();
|
|
||||||
// // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
// }, []);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const products = await api.getSinglePaymentProducts({ token });
|
const products = await api.getSinglePaymentProducts({ token });
|
||||||
|
|||||||
@ -10,9 +10,28 @@ import { useSelector } from "react-redux";
|
|||||||
interface IUsePaymentProps {
|
interface IUsePaymentProps {
|
||||||
placementKey: EPlacementKeys;
|
placementKey: EPlacementKeys;
|
||||||
activeProduct: IPaywallProduct;
|
activeProduct: IPaywallProduct;
|
||||||
|
paymentFormType?: "lightbox" | "inline";
|
||||||
|
cardNumberRef?: React.RefObject<HTMLDivElement>;
|
||||||
|
cardExpiryRef?: React.RefObject<HTMLDivElement>;
|
||||||
|
cardCvvRef?: React.RefObject<HTMLDivElement>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) => {
|
interface IFieldValidation {
|
||||||
|
isValid: boolean;
|
||||||
|
message: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IFormValidation {
|
||||||
|
ccnumber: IFieldValidation;
|
||||||
|
ccexp: IFieldValidation;
|
||||||
|
cvv: IFieldValidation;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const usePayment = ({
|
||||||
|
placementKey,
|
||||||
|
activeProduct,
|
||||||
|
paymentFormType = "lightbox",
|
||||||
|
}: IUsePaymentProps) => {
|
||||||
const api = useApi();
|
const api = useApi();
|
||||||
const token = useSelector(selectors.selectToken);
|
const token = useSelector(selectors.selectToken);
|
||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
@ -22,6 +41,15 @@ export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) =>
|
|||||||
const formPrice = String((activeProduct?.trialPrice || 99) / 100);
|
const formPrice = String((activeProduct?.trialPrice || 99) / 100);
|
||||||
const [isOpenModal, setIsOpenModal] = useState(false);
|
const [isOpenModal, setIsOpenModal] = useState(false);
|
||||||
const [isModalClosed, setIsModalClosed] = useState(false);
|
const [isModalClosed, setIsModalClosed] = useState(false);
|
||||||
|
const [formValidation, setFormValidation] = useState<IFormValidation>({
|
||||||
|
ccnumber: { isValid: false, message: '' },
|
||||||
|
ccexp: { isValid: false, message: '' },
|
||||||
|
cvv: { isValid: false, message: '' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const isFormValid = useMemo(() => {
|
||||||
|
return Object.values(formValidation).every(field => field.isValid);
|
||||||
|
}, [formValidation]);
|
||||||
|
|
||||||
const updatePaymentModalState = () => {
|
const updatePaymentModalState = () => {
|
||||||
setIsOpenModal(false);
|
setIsOpenModal(false);
|
||||||
@ -39,41 +67,53 @@ export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
if (!activeProduct || !products.length) return;
|
||||||
const product = products.find((product) => product._id === activeProduct._id);
|
const product = products.find((product) => product._id === activeProduct._id);
|
||||||
if (!product) {
|
if (!product) {
|
||||||
setError("Product not found");
|
setError("Product not found");
|
||||||
} else {
|
} else {
|
||||||
setError(null);
|
setError(null);
|
||||||
}
|
}
|
||||||
}, [products]);
|
}, [products, activeProduct]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeProduct) return;
|
if (!activeProduct) return;
|
||||||
window.CollectJS.configure({
|
|
||||||
variant: 'lightbox',
|
const config: any = {
|
||||||
|
variant: paymentFormType,
|
||||||
callback: (token: any) => {
|
callback: (token: any) => {
|
||||||
finishSubmit(token);
|
finishSubmit(token);
|
||||||
},
|
},
|
||||||
|
paymentSelector: '#customPayButton',
|
||||||
primaryColor: '#066fde',
|
primaryColor: '#066fde',
|
||||||
theme: "material",
|
theme: "material",
|
||||||
|
price: formPrice,
|
||||||
fields: {
|
fields: {
|
||||||
ccnumber: {
|
ccnumber: {
|
||||||
placeholder: '1234 1234 1234 1234',
|
placeholder: '1234 1234 1234 1234',
|
||||||
selector: '#ccnumber'
|
selector: '#card-number'
|
||||||
},
|
},
|
||||||
ccexp: {
|
ccexp: {
|
||||||
placeholder: 'MM/YY',
|
placeholder: 'MM/YY',
|
||||||
selector: '#ccexp'
|
selector: '#card-expiry'
|
||||||
},
|
},
|
||||||
cvv: {
|
cvv: {
|
||||||
placeholder: 'CVC',
|
placeholder: 'CVC',
|
||||||
selector: '#cvv'
|
selector: '#card-cvv'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
price: formPrice,
|
validationCallback: (field: string, status: boolean, message: string) => {
|
||||||
// country: "US",
|
setFormValidation(prev => ({
|
||||||
// currency: "USD",
|
...prev,
|
||||||
});
|
[field]: {
|
||||||
|
isValid: status,
|
||||||
|
message: status ? '' : message
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
window.CollectJS?.configure(config);
|
||||||
}, [placementId, paywallId, activeProduct]);
|
}, [placementId, paywallId, activeProduct]);
|
||||||
|
|
||||||
const finishSubmit = async (response: any) => {
|
const finishSubmit = async (response: any) => {
|
||||||
@ -106,6 +146,24 @@ export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) =>
|
|||||||
setIsOpenModal(true)
|
setIsOpenModal(true)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const submitInlineForm = () => {
|
||||||
|
try {
|
||||||
|
setIsSubmitting(true);
|
||||||
|
console.log("submitInlineForm");
|
||||||
|
|
||||||
|
window.CollectJS.startPaymentRequest();
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('Payment form error:', error);
|
||||||
|
setError(error?.message);
|
||||||
|
setIsSubmitting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
setIsOpenModal(false);
|
||||||
|
setIsModalClosed(true);
|
||||||
|
}
|
||||||
|
|
||||||
return useMemo(() => ({
|
return useMemo(() => ({
|
||||||
isLoading,
|
isLoading,
|
||||||
paymentResponse,
|
paymentResponse,
|
||||||
@ -113,7 +171,11 @@ export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) =>
|
|||||||
isPaymentSuccess,
|
isPaymentSuccess,
|
||||||
isOpenModal,
|
isOpenModal,
|
||||||
isModalClosed,
|
isModalClosed,
|
||||||
showCreditCardForm
|
showCreditCardForm,
|
||||||
|
submitInlineForm,
|
||||||
|
closeModal,
|
||||||
|
formValidation,
|
||||||
|
isFormValid
|
||||||
}), [
|
}), [
|
||||||
isLoading,
|
isLoading,
|
||||||
paymentResponse,
|
paymentResponse,
|
||||||
@ -121,6 +183,10 @@ export const usePayment = ({ placementKey, activeProduct }: IUsePaymentProps) =>
|
|||||||
isPaymentSuccess,
|
isPaymentSuccess,
|
||||||
isOpenModal,
|
isOpenModal,
|
||||||
isModalClosed,
|
isModalClosed,
|
||||||
showCreditCardForm
|
showCreditCardForm,
|
||||||
|
submitInlineForm,
|
||||||
|
closeModal,
|
||||||
|
formValidation,
|
||||||
|
isFormValid
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
@ -692,4 +692,75 @@ export const defaultPaywalls: { [key in EPlacementKeys]: IPaywall } = {
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
"aura.placement.email.palmistry": {
|
||||||
|
"_id": "678be264aaa17756a1e517de",
|
||||||
|
"key": "aura.paywall.email.palmistry.main",
|
||||||
|
"name": "Email Palmistry",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"key": "full.price",
|
||||||
|
"value": "45",
|
||||||
|
"_id": "678be264aaa17756a1e517df"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"aura.placement.email.palmistry.discount": {
|
||||||
|
"_id": "678be2b9aaa17756a1e51faa",
|
||||||
|
"key": "aura.paywall.email.palmistry.discount",
|
||||||
|
"name": "Email Palmistry Discount",
|
||||||
|
"properties": [
|
||||||
|
{
|
||||||
|
"key": "full.price",
|
||||||
|
"value": "45",
|
||||||
|
"_id": "678be264aaa17756a1e517df"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "discount",
|
||||||
|
"value": "70",
|
||||||
|
"_id": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"products": [
|
||||||
|
{
|
||||||
|
"_id": "66589439ef0d180993cdb72f",
|
||||||
|
"key": "compatibility.secret.discount.trial.0",
|
||||||
|
"productId": "prod_PnStTEBzrPLgvL",
|
||||||
|
"name": "Сompatibility AURA Secret Discount | Trial $0.99",
|
||||||
|
"priceId": "price_1PpFlMIlX4lgwUxrUTeWDFoI",
|
||||||
|
"type": "subscription",
|
||||||
|
"description": "Description",
|
||||||
|
"discountPrice": null,
|
||||||
|
"discountPriceId": null,
|
||||||
|
"isDiscount": false,
|
||||||
|
"isFreeTrial": false,
|
||||||
|
"isTrial": true,
|
||||||
|
"price": 900,
|
||||||
|
"trialDuration": 3,
|
||||||
|
"trialPrice": 100,
|
||||||
|
"trialPriceId": "price_1PpFoNIlX4lgwUxrP4l0lbE5",
|
||||||
|
"currency": "usd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -16,7 +16,7 @@ export const useTranslations = (
|
|||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const { t, i18n } = useTranslation();
|
const { t, i18n } = useTranslation();
|
||||||
const gender =
|
const gender =
|
||||||
useSelector(selectors.selectQuestionnaire)?.gender || "default";
|
useSelector(selectors.selectQuestionnaire)?.gender || "male";
|
||||||
const { flags } = useMetricABFlags();
|
const { flags } = useMetricABFlags();
|
||||||
const esFlag = flags?.esFlag?.[0];
|
const esFlag = flags?.esFlag?.[0];
|
||||||
|
|
||||||
|
|||||||
18
src/init.tsx
18
src/init.tsx
@ -12,11 +12,9 @@ import {
|
|||||||
buildResources,
|
buildResources,
|
||||||
fallbackLng,
|
fallbackLng,
|
||||||
getDefaultLocaleByLanguage,
|
getDefaultLocaleByLanguage,
|
||||||
getTranslationJSON,
|
getTranslationsJSON,
|
||||||
ELocalesPlacement,
|
|
||||||
language,
|
language,
|
||||||
setLanguage,
|
setLanguage,
|
||||||
TTranslationPlacements,
|
|
||||||
} from "./locales";
|
} from "./locales";
|
||||||
import App from "./components/App";
|
import App from "./components/App";
|
||||||
import metricService from "./services/metric/metricService";
|
import metricService from "./services/metric/metricService";
|
||||||
@ -39,16 +37,10 @@ const init = async () => {
|
|||||||
api.getAppConfig({ bundleId: "auraweb" }),
|
api.getAppConfig({ bundleId: "auraweb" }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const localePlacements = Object.values(ELocalesPlacement);
|
console.time('translations-loading');
|
||||||
|
const translationsPlacement = await getTranslationsJSON(language);
|
||||||
const translationPlacements: TTranslationPlacements = {};
|
console.timeEnd('translations-loading');
|
||||||
|
const resources = buildResources(translationsResponse, translationsPlacement);
|
||||||
for (const placement of localePlacements) {
|
|
||||||
const translationsPlacement = await getTranslationJSON(placement, language);
|
|
||||||
translationPlacements[placement] = translationsPlacement;
|
|
||||||
}
|
|
||||||
|
|
||||||
const resources = buildResources(translationsResponse, translationPlacements);
|
|
||||||
|
|
||||||
const legal = buildLegal(elementsResponse);
|
const legal = buildLegal(elementsResponse);
|
||||||
const config = configResponse.data;
|
const config = configResponse.data;
|
||||||
|
|||||||
@ -70,58 +70,49 @@ export enum ELocalesPlacement {
|
|||||||
PalmistryV0 = "palmistry-v0",
|
PalmistryV0 = "palmistry-v0",
|
||||||
PalmistryV01 = "palmistry-v0_1",
|
PalmistryV01 = "palmistry-v0_1",
|
||||||
PalmistryV1 = "palmistry-v1",
|
PalmistryV1 = "palmistry-v1",
|
||||||
|
PalmistryV11 = "palmistry-v1_1",
|
||||||
Chats = "chats"
|
Chats = "chats"
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ITranslationJSON {
|
interface ITranslationJSON {
|
||||||
male: { [key: string]: string }
|
male: { [key: string]: string }
|
||||||
female: { [key: string]: string }
|
female: { [key: string]: string }
|
||||||
default: { [key: string]: string }
|
fallback: { male: { [key: string]: string }; female: { [key: string]: string } }
|
||||||
fallback: { male: { [key: string]: string }; female: { [key: string]: string }, default: { [key: string]: string } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type TTranslationPlacements = Partial<
|
export type TTranslationPlacements = Partial<
|
||||||
Record<ELocalesPlacement, ITranslationJSON>
|
Record<ELocalesPlacement, ITranslationJSON>
|
||||||
>
|
>
|
||||||
|
|
||||||
export const getTranslationJSON = async (placement: ELocalesPlacement | undefined, language: string): Promise<ITranslationJSON> => {
|
export const getTranslationsJSON = async (language: string): Promise<TTranslationPlacements> => {
|
||||||
const protocol = window.location.protocol;
|
const api = createApi();
|
||||||
const host = window.location.host;
|
|
||||||
let defaultLanguage = getDefaultLocaleByLanguage(language).toLowerCase();
|
let defaultLanguage = getDefaultLocaleByLanguage(language).toLowerCase();
|
||||||
if (defaultLanguage === "pt") {
|
if (defaultLanguage === "pt") {
|
||||||
defaultLanguage = "pt-pt"
|
defaultLanguage = "pt-pt"
|
||||||
}
|
}
|
||||||
const localePlacement = placement || ELocalesPlacement.V1
|
|
||||||
let result;
|
|
||||||
try {
|
|
||||||
const [
|
|
||||||
resultMale,
|
|
||||||
resultFemale,
|
|
||||||
resultMaleFallback,
|
|
||||||
resultFemaleFallback,
|
|
||||||
] = await Promise.all([
|
|
||||||
(await fetch(`${protocol}//${host}/locales/${localePlacement}/${defaultLanguage}/male_${defaultLanguage}.json`)).json(),
|
|
||||||
(await fetch(`${protocol}//${host}/locales/${localePlacement}/${defaultLanguage}/female_${defaultLanguage}.json`)).json(),
|
|
||||||
(await fetch(`${protocol}//${host}/locales/${localePlacement}/en/male_en.json`)).json(),
|
|
||||||
(await fetch(`${protocol}//${host}/locales/${localePlacement}/en/female_en.json`)).json()
|
|
||||||
]);
|
|
||||||
|
|
||||||
result = {
|
const placements = Object.values(ELocalesPlacement).filter(placement => placement !== ELocalesPlacement.V0);
|
||||||
male: resultMale, female: resultFemale, default: resultMale, fallback: {
|
|
||||||
male: resultMaleFallback, female: resultFemaleFallback, default: resultMaleFallback
|
try {
|
||||||
}
|
const responses = await Promise.all(
|
||||||
}
|
placements.map(place =>
|
||||||
|
api.getLocaleTranslations({
|
||||||
|
funnel: place,
|
||||||
|
locale: defaultLanguage
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = responses.reduce((merged, current, index) => ({
|
||||||
|
...merged,
|
||||||
|
[placements[index]]: current
|
||||||
|
}), {});
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (language !== fallbackLng) {
|
console.error('Translation loading error:', error);
|
||||||
result = await getTranslationJSON(localePlacement, fallbackLng)
|
return {};
|
||||||
}
|
|
||||||
result = {
|
|
||||||
male: {}, female: {}, default: {}, fallback: {
|
|
||||||
male: {}, female: {}, default: {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const buildResources = (resp: Translations.Response, translationJSON: TTranslationPlacements) => {
|
export const buildResources = (resp: Translations.Response, translationJSON: TTranslationPlacements) => {
|
||||||
|
|||||||
@ -2,11 +2,10 @@ import Header from "@/components/pages/ABDesign/v1/components/Header";
|
|||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
|
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { Outlet, useLocation, useSearchParams } from "react-router-dom";
|
import { Outlet, useLocation } from "react-router-dom";
|
||||||
import routes from "@/routes";
|
import routes from "@/routes";
|
||||||
import PaymentModal from "@/components/PalmistryV1/components/PaymentModal";
|
import { useDispatch } from "react-redux";
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
import { actions } from "@/store";
|
||||||
import { actions, selectors } from "@/store";
|
|
||||||
|
|
||||||
const isBackButtonVisibleRoutes = [
|
const isBackButtonVisibleRoutes = [
|
||||||
routes.client.palmistryV1Birthdate(),
|
routes.client.palmistryV1Birthdate(),
|
||||||
@ -40,8 +39,6 @@ const headerClassNames = {
|
|||||||
|
|
||||||
function LayoutPalmistryV2() {
|
function LayoutPalmistryV2() {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const token = useSelector(selectors.selectToken);
|
|
||||||
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
|
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const mainRef = useRef<HTMLDivElement>(null);
|
const mainRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
@ -56,12 +53,6 @@ function LayoutPalmistryV2() {
|
|||||||
useSchemeColorByElement(mainRef.current, "section.page, .page, section", [
|
useSchemeColorByElement(mainRef.current, "section.page, .page, section", [
|
||||||
location,
|
location,
|
||||||
]);
|
]);
|
||||||
const isShowPaymentModal = useSelector(
|
|
||||||
selectors.selectPalmistryIsShowPaymentModalV1
|
|
||||||
);
|
|
||||||
const [searchParams] = useSearchParams();
|
|
||||||
const subscriptionStatus =
|
|
||||||
searchParams.get("redirect_status") === "succeeded" ? "subscribed" : "lead";
|
|
||||||
|
|
||||||
const getIsBackButtonVisible = () => {
|
const getIsBackButtonVisible = () => {
|
||||||
for (const route of isBackButtonVisibleRoutes) {
|
for (const route of isBackButtonVisibleRoutes) {
|
||||||
@ -87,19 +78,6 @@ function LayoutPalmistryV2() {
|
|||||||
{/* <Suspense fallback={<LoadingPage />}> */}
|
{/* <Suspense fallback={<LoadingPage />}> */}
|
||||||
<section className={styles.page}>
|
<section className={styles.page}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
{!!token.length && !!activeProductFromStore && (
|
|
||||||
// [
|
|
||||||
// routes.client.palmistryV1Payment(),
|
|
||||||
// routes.client.palmistryV1TrialPayment(),
|
|
||||||
// ].includes(location.pathname) &&
|
|
||||||
<PaymentModal
|
|
||||||
className={
|
|
||||||
isShowPaymentModal || subscriptionStatus === "subscribed"
|
|
||||||
? styles["payment-modal-active"]
|
|
||||||
: styles["payment-modal-hide"]
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
{/* </Suspense> */}
|
{/* </Suspense> */}
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@ -5,11 +5,15 @@
|
|||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
max-width: 560px;
|
max-width: 560px;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
overflow-x: hidden;
|
// overflow-x: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.header {
|
.header {
|
||||||
padding: 8px 0 30px;
|
padding: 8px 0 30px;
|
||||||
|
|
||||||
|
& > button {
|
||||||
|
margin-left: -12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.header-title {
|
.header-title {
|
||||||
|
|||||||
@ -7,17 +7,20 @@ const host = "";
|
|||||||
export const apiHost = environments.AURA_API_HOST;
|
export const apiHost = environments.AURA_API_HOST;
|
||||||
const dApiHost = environments.AURA_DAPI_HOST;
|
const dApiHost = environments.AURA_DAPI_HOST;
|
||||||
const dApiPrefix = environments.AURA_DAPI_PREFIX;
|
const dApiPrefix = environments.AURA_DAPI_PREFIX;
|
||||||
const siteHost = environments.AURA_SITE_HOST;
|
// const siteHost = environments.AURA_SITE_HOST;
|
||||||
const prefix = environments.AURA_PREFIX;
|
const prefix = environments.AURA_PREFIX;
|
||||||
const openAIHost = environments.AURA_OPEN_AI_HOST;
|
const openAIHost = environments.AURA_OPEN_AI_HOST;
|
||||||
const openAiPrefix = environments.AURA_OPEN_AI_PREFIX;
|
const openAiPrefix = environments.AURA_OPEN_AI_PREFIX;
|
||||||
|
|
||||||
export const palmistryV1Prefix = [host, "v1", "palmistry"].join("/")
|
export const palmistryV1Prefix = [host, "v1", "palmistry"].join("/")
|
||||||
export const palmistryV2Prefix = [host, "v2", "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 emailMarketingV1Prefix = [host, "v1", "email-marketing"].join("/")
|
||||||
|
|
||||||
export const chatsPrefix = [host, "chats"].join("/")
|
export const chatsPrefix = [host, "chats"].join("/")
|
||||||
|
|
||||||
|
export const oldBackendPrefix = [`${window.location.protocol}/`, window.location.host, "old-backend"].join("/")
|
||||||
|
|
||||||
const routes = {
|
const routes = {
|
||||||
client: {
|
client: {
|
||||||
root: () => [host, ""].join("/"),
|
root: () => [host, ""].join("/"),
|
||||||
@ -55,7 +58,7 @@ const routes = {
|
|||||||
emailEnter: () => [host, "email"].join("/"),
|
emailEnter: () => [host, "email"].join("/"),
|
||||||
authResult: () => [host, "auth", "result"].join("/"),
|
authResult: () => [host, "auth", "result"].join("/"),
|
||||||
auth: () => [host, "auth"].join("/"),
|
auth: () => [host, "auth"].join("/"),
|
||||||
subscription: () => [host, "subscription"].join("/"),
|
// subscription: () => [host, "subscription"].join("/"),
|
||||||
createProfile: () => [host, "profile", "create"].join("/"),
|
createProfile: () => [host, "profile", "create"].join("/"),
|
||||||
attention: () => [host, "attention"].join("/"),
|
attention: () => [host, "attention"].join("/"),
|
||||||
feedback: () => [host, "feedback"].join("/"),
|
feedback: () => [host, "feedback"].join("/"),
|
||||||
@ -64,7 +67,7 @@ const routes = {
|
|||||||
paymentSuccess: () => [host, "payment", "success"].join("/"),
|
paymentSuccess: () => [host, "payment", "success"].join("/"),
|
||||||
paymentFail: () => [host, "payment", "fail"].join("/"),
|
paymentFail: () => [host, "payment", "fail"].join("/"),
|
||||||
wallpaper: () => [host, "wallpaper"].join("/"),
|
wallpaper: () => [host, "wallpaper"].join("/"),
|
||||||
static: () => [host, "static", ":typeId"].join("/"),
|
// static: () => [host, "static", ":typeId"].join("/"),
|
||||||
legal: (type: string) => [host, "static", type].join("/"),
|
legal: (type: string) => [host, "static", type].join("/"),
|
||||||
compatibility: () => [host, "compatibility"].join("/"),
|
compatibility: () => [host, "compatibility"].join("/"),
|
||||||
compatibilityResult: () => [host, "compatibility", "result"].join("/"),
|
compatibilityResult: () => [host, "compatibility", "result"].join("/"),
|
||||||
@ -187,9 +190,9 @@ const routes = {
|
|||||||
palmistryV1Payment: () => [palmistryV1Prefix, "payment"].join("/"),
|
palmistryV1Payment: () => [palmistryV1Prefix, "payment"].join("/"),
|
||||||
palmistryOnboardingV1: () => [palmistryV1Prefix, "onboarding"].join("/"),
|
palmistryOnboardingV1: () => [palmistryV1Prefix, "onboarding"].join("/"),
|
||||||
// PalmistryV2
|
// PalmistryV2
|
||||||
palmistryV2TrialPayment: () => [palmistryV2Prefix, "trial-payment"].join("/"),
|
palmistryV2TrialPayment: () => [palmistryEmailMarketingV2Prefix, "trial-payment"].join("/"),
|
||||||
palmistryV2SaveOff: () => [palmistryV2Prefix, "save-off"].join("/"),
|
palmistryV2SaveOff: () => [palmistryEmailMarketingV2Prefix, "save-off"].join("/"),
|
||||||
palmistryV2SecretDiscount: () => [palmistryV2Prefix, "secret-discount"].join("/"),
|
palmistryV2SecretDiscount: () => [palmistryEmailMarketingV2Prefix, "secret-discount"].join("/"),
|
||||||
// MarketingLandingV1
|
// MarketingLandingV1
|
||||||
emailMarketingV1Landing: () => [emailMarketingV1Prefix, "marketing-landing"].join("/"),
|
emailMarketingV1Landing: () => [emailMarketingV1Prefix, "marketing-landing"].join("/"),
|
||||||
emailMarketingV1SpecialOffer: () => [emailMarketingV1Prefix, "special-offer"].join("/"),
|
emailMarketingV1SpecialOffer: () => [emailMarketingV1Prefix, "special-offer"].join("/"),
|
||||||
@ -299,19 +302,15 @@ const routes = {
|
|||||||
},
|
},
|
||||||
server: {
|
server: {
|
||||||
userLocale: () => ["https://ipapi.co", "json"].join("/"),
|
userLocale: () => ["https://ipapi.co", "json"].join("/"),
|
||||||
appleAuth: (origin: string) =>
|
|
||||||
[apiHost, "auth", "apple", `gate?origin=${origin}`].join("/"),
|
|
||||||
googleAuth: (origin: string) =>
|
|
||||||
[apiHost, "auth", "google", `gate?origin=${origin}`].join("/"),
|
|
||||||
user: () => [apiHost, prefix, "user.json"].join("/"),
|
user: () => [apiHost, prefix, "user.json"].join("/"),
|
||||||
token: () => [apiHost, prefix, "auth", "token.json"].join("/"),
|
// token: () => [apiHost, prefix, "auth", "token.json"].join("/"),
|
||||||
elements: () => [apiHost, prefix, "elements.json"].join("/"),
|
elements: () => [oldBackendPrefix, "elements.json"].join("/"),
|
||||||
zodiacs: (zodiac: string) =>
|
zodiacs: (zodiac: string) =>
|
||||||
[apiHost, prefix, "zodiacs", `${zodiac}.json`].join("/"),
|
[apiHost, prefix, "zodiacs", `${zodiac}.json`].join("/"),
|
||||||
element: (type: string) =>
|
// element: (type: string) =>
|
||||||
[apiHost, prefix, "elements", `${type}.json`].join("/"),
|
// [apiHost, prefix, "elements", `${type}.json`].join("/"),
|
||||||
apps: (bundleId: string) =>
|
apps: (bundleId: string) =>
|
||||||
[apiHost, prefix, "apps", `${bundleId}.json`].join("/"),
|
[oldBackendPrefix, `${bundleId}.json`].join("/"),
|
||||||
assets: (category: string) =>
|
assets: (category: string) =>
|
||||||
[apiHost, prefix, "assets", "categories", `${category}.json`].join("/"),
|
[apiHost, prefix, "assets", "categories", `${category}.json`].join("/"),
|
||||||
assetCategories: () =>
|
assetCategories: () =>
|
||||||
@ -319,15 +318,13 @@ const routes = {
|
|||||||
dailyForecasts: () =>
|
dailyForecasts: () =>
|
||||||
[apiHost, prefix, "user", "daily_forecast.json"].join("/"),
|
[apiHost, prefix, "user", "daily_forecast.json"].join("/"),
|
||||||
auras: () => [apiHost, prefix, "user", "aura.json"].join("/"),
|
auras: () => [apiHost, prefix, "user", "aura.json"].join("/"),
|
||||||
paymentIntents: () =>
|
// subscriptionItems: () =>
|
||||||
[apiHost, prefix, "user", "payment_intents.json"].join("/"),
|
// [apiHost, prefix, "user", "subscription", "item_prices.json"].join("/"),
|
||||||
subscriptionItems: () =>
|
// subscriptionPlans: () => [apiHost, prefix, "sub_plans.json"].join("/"),
|
||||||
[apiHost, prefix, "user", "subscription", "item_prices.json"].join("/"),
|
// subscriptionCheckout: () =>
|
||||||
subscriptionPlans: () => [apiHost, prefix, "sub_plans.json"].join("/"),
|
// [apiHost, prefix, "user", "subscription", "checkout", "new.json"].join(
|
||||||
subscriptionCheckout: () =>
|
// "/"
|
||||||
[apiHost, prefix, "user", "subscription", "checkout", "new.json"].join(
|
// ),
|
||||||
"/"
|
|
||||||
),
|
|
||||||
subscriptionStatus: () =>
|
subscriptionStatus: () =>
|
||||||
[apiHost, prefix, "user", "subscription_receipts", "status.json"].join(
|
[apiHost, prefix, "user", "subscription_receipts", "status.json"].join(
|
||||||
"/"
|
"/"
|
||||||
@ -336,10 +333,10 @@ const routes = {
|
|||||||
[dApiHost, "users", "subscription", "status"].join("/"),
|
[dApiHost, "users", "subscription", "status"].join("/"),
|
||||||
subscriptionReceipts: () =>
|
subscriptionReceipts: () =>
|
||||||
[apiHost, prefix, "user", "subscription_receipts.json"].join("/"),
|
[apiHost, prefix, "user", "subscription_receipts.json"].join("/"),
|
||||||
subscriptionReceipt: (id: string) =>
|
// subscriptionReceipt: (id: string) =>
|
||||||
[apiHost, prefix, "user", "subscription_receipts", `${id}.json`].join(
|
// [apiHost, prefix, "user", "subscription_receipts", `${id}.json`].join(
|
||||||
"/"
|
// "/"
|
||||||
),
|
// ),
|
||||||
compatCategories: () =>
|
compatCategories: () =>
|
||||||
[apiHost, prefix, "ai", "compat_categories.json"].join("/"),
|
[apiHost, prefix, "ai", "compat_categories.json"].join("/"),
|
||||||
compat: () => [apiHost, prefix, "ai", "compats.json"].join("/"),
|
compat: () => [apiHost, prefix, "ai", "compats.json"].join("/"),
|
||||||
@ -347,7 +344,8 @@ const routes = {
|
|||||||
[apiHost, prefix, "user", "callbacks.json"].join("/"),
|
[apiHost, prefix, "user", "callbacks.json"].join("/"),
|
||||||
getUserCallbacks: (id: string) =>
|
getUserCallbacks: (id: string) =>
|
||||||
[apiHost, prefix, "user", "callbacks", `${id}.json`].join("/"),
|
[apiHost, prefix, "user", "callbacks", `${id}.json`].join("/"),
|
||||||
getTranslations: () => [siteHost, "api/v2", "t.json"].join("/"),
|
getTranslations: () => [oldBackendPrefix, "t.json"].join("/"),
|
||||||
|
// getTranslations: () => [siteHost, "api/v2", "t.json"].join("/"),
|
||||||
aiRequestsV2: (promptKey: string) =>
|
aiRequestsV2: (promptKey: string) =>
|
||||||
[apiHost, "api/v2", "ai", "prompts", promptKey, "requests.json"].join(
|
[apiHost, "api/v2", "ai", "prompts", promptKey, "requests.json"].join(
|
||||||
"/"
|
"/"
|
||||||
@ -395,6 +393,7 @@ const routes = {
|
|||||||
// Session
|
// Session
|
||||||
createSession: () => [dApiHost, dApiPrefix, "session"].join("/"),
|
createSession: () => [dApiHost, dApiPrefix, "session"].join("/"),
|
||||||
updateSession: (id: string) => [dApiHost, dApiPrefix, "session", id].join("/"),
|
updateSession: (id: string) => [dApiHost, dApiPrefix, "session", id].join("/"),
|
||||||
|
getLocale: () => [dApiHost, dApiPrefix, "session", "locale"].join("/"),
|
||||||
|
|
||||||
// Chats
|
// Chats
|
||||||
getChatsCategories: () => [dApiHost, "chats", "categories"].join("/"),
|
getChatsCategories: () => [dApiHost, "chats", "categories"].join("/"),
|
||||||
@ -422,7 +421,7 @@ const routes = {
|
|||||||
export const entrypoints = [
|
export const entrypoints = [
|
||||||
routes.client.root(),
|
routes.client.root(),
|
||||||
routes.client.birthday(),
|
routes.client.birthday(),
|
||||||
routes.client.subscription(),
|
// routes.client.subscription(),
|
||||||
routes.client.wallpaper(),
|
routes.client.wallpaper(),
|
||||||
routes.client.didYouKnow(),
|
routes.client.didYouKnow(),
|
||||||
routes.client.attention(),
|
routes.client.attention(),
|
||||||
@ -451,7 +450,7 @@ export const hasNoNavigation = (path: string) => !hasNavigation(path);
|
|||||||
|
|
||||||
export const withCrossButtonRoutes = [
|
export const withCrossButtonRoutes = [
|
||||||
// routes.client.attention(),
|
// routes.client.attention(),
|
||||||
routes.client.subscription(),
|
// routes.client.subscription(),
|
||||||
routes.client.paymentMethod(),
|
routes.client.paymentMethod(),
|
||||||
];
|
];
|
||||||
/**
|
/**
|
||||||
@ -562,7 +561,7 @@ export const withoutHeaderRoutes = [
|
|||||||
routes.client.palmistryDiscount(),
|
routes.client.palmistryDiscount(),
|
||||||
routes.client.palmistryPremiumBundle(),
|
routes.client.palmistryPremiumBundle(),
|
||||||
routes.client.compatibility(),
|
routes.client.compatibility(),
|
||||||
routes.client.subscription(),
|
// routes.client.subscription(),
|
||||||
routes.client.paymentMethod(),
|
routes.client.paymentMethod(),
|
||||||
routes.client.paymentResult(),
|
routes.client.paymentResult(),
|
||||||
routes.client.paymentSuccess(),
|
routes.client.paymentSuccess(),
|
||||||
|
|||||||
@ -1,4 +1,9 @@
|
|||||||
import { ITrial } from "@/api/resources/SubscriptionPlans";
|
interface ITrial {
|
||||||
|
is_paid: boolean;
|
||||||
|
is_free: boolean;
|
||||||
|
days: number;
|
||||||
|
price_cents: number;
|
||||||
|
}
|
||||||
|
|
||||||
export const roundToWhole = (value: string | number): number => {
|
export const roundToWhole = (value: string | number): number => {
|
||||||
value = Number(value);
|
value = Number(value);
|
||||||
|
|||||||
@ -39,14 +39,7 @@ import onboardingConfig, {
|
|||||||
import payment, {
|
import payment, {
|
||||||
actions as paymentActions,
|
actions as paymentActions,
|
||||||
selectActiveProduct,
|
selectActiveProduct,
|
||||||
selectIsDiscount,
|
|
||||||
selectStripeButton,
|
|
||||||
selectSubscriptionReceipt,
|
|
||||||
} from "./payment";
|
} from "./payment";
|
||||||
import subscriptionPlans, {
|
|
||||||
actions as subscriptionPlasActions,
|
|
||||||
selectPlanById,
|
|
||||||
} from "./subscriptionPlan";
|
|
||||||
import status, { actions as userStatusActions, selectStatus } from "./status";
|
import status, { actions as userStatusActions, selectStatus } from "./status";
|
||||||
import compatibility, {
|
import compatibility, {
|
||||||
actions as compatibilityActions,
|
actions as compatibilityActions,
|
||||||
@ -100,7 +93,6 @@ export const actions = {
|
|||||||
user: userActions,
|
user: userActions,
|
||||||
form: formActions,
|
form: formActions,
|
||||||
status: userStatusActions,
|
status: userStatusActions,
|
||||||
subscriptionPlan: subscriptionPlasActions,
|
|
||||||
aura: auraActions,
|
aura: auraActions,
|
||||||
paywalls: paywallsActions,
|
paywalls: paywallsActions,
|
||||||
siteConfig: siteConfigActions,
|
siteConfig: siteConfigActions,
|
||||||
@ -126,7 +118,6 @@ export const selectors = {
|
|||||||
selectUTM,
|
selectUTM,
|
||||||
selectUser,
|
selectUser,
|
||||||
selectStatus,
|
selectStatus,
|
||||||
selectPlanById,
|
|
||||||
selectAuraCoordinates,
|
selectAuraCoordinates,
|
||||||
selectRightUser,
|
selectRightUser,
|
||||||
selectSelfName,
|
selectSelfName,
|
||||||
@ -136,8 +127,6 @@ export const selectors = {
|
|||||||
selectUserCallbacksDescription,
|
selectUserCallbacksDescription,
|
||||||
selectUserCallbacksPrevStat,
|
selectUserCallbacksPrevStat,
|
||||||
selectHome,
|
selectHome,
|
||||||
selectIsDiscount,
|
|
||||||
selectSubscriptionReceipt,
|
|
||||||
selectOnboarding,
|
selectOnboarding,
|
||||||
selectOnboardingHome,
|
selectOnboardingHome,
|
||||||
selectOnboardingCompatibility,
|
selectOnboardingCompatibility,
|
||||||
@ -160,7 +149,6 @@ export const selectors = {
|
|||||||
selectPaywalls,
|
selectPaywalls,
|
||||||
selectPaywallsIsMustUpdate,
|
selectPaywallsIsMustUpdate,
|
||||||
selectPrivacyPolicy,
|
selectPrivacyPolicy,
|
||||||
selectStripeButton,
|
|
||||||
selectPersonalVideo,
|
selectPersonalVideo,
|
||||||
selectCurrency,
|
selectCurrency,
|
||||||
selectPalmistryFromRedesign,
|
selectPalmistryFromRedesign,
|
||||||
@ -183,7 +171,6 @@ export const reducer = combineReducers({
|
|||||||
user,
|
user,
|
||||||
form,
|
form,
|
||||||
status,
|
status,
|
||||||
subscriptionPlans,
|
|
||||||
aura,
|
aura,
|
||||||
payment,
|
payment,
|
||||||
compatibility,
|
compatibility,
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import { IPaywallProduct } from "@/api/resources/Paywall";
|
import { IPaywallProduct } from "@/api/resources/Paywall";
|
||||||
import { SubscriptionReceipt } from "@/api/resources/UserSubscriptionReceipts";
|
|
||||||
import { TCanMakePaymentResult } from "@/hooks/payment/useCanUseStripeButton";
|
import { TCanMakePaymentResult } from "@/hooks/payment/useCanUseStripeButton";
|
||||||
import { createSlice, createSelector } from "@reduxjs/toolkit";
|
import { createSlice, createSelector } from "@reduxjs/toolkit";
|
||||||
import type { PayloadAction } from "@reduxjs/toolkit";
|
import type { PayloadAction } from "@reduxjs/toolkit";
|
||||||
@ -12,21 +11,12 @@ interface IStripeButton {
|
|||||||
|
|
||||||
interface IPayment {
|
interface IPayment {
|
||||||
selectedPrice: number | null;
|
selectedPrice: number | null;
|
||||||
isDiscount: boolean;
|
|
||||||
subscriptionReceipt: SubscriptionReceipt | null;
|
|
||||||
activeProduct: IPaywallProduct | null;
|
activeProduct: IPaywallProduct | null;
|
||||||
stripeButton: IStripeButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: IPayment = {
|
const initialState: IPayment = {
|
||||||
selectedPrice: null,
|
selectedPrice: null,
|
||||||
isDiscount: false,
|
|
||||||
subscriptionReceipt: null,
|
|
||||||
activeProduct: null,
|
activeProduct: null,
|
||||||
stripeButton: {
|
|
||||||
paymentRequest: null,
|
|
||||||
availableMethods: null,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const paymentSlice = createSlice({
|
const paymentSlice = createSlice({
|
||||||
@ -52,16 +42,4 @@ export const selectActiveProduct = createSelector(
|
|||||||
(state: { payment: IPayment }) => state.payment.activeProduct,
|
(state: { payment: IPayment }) => state.payment.activeProduct,
|
||||||
(payment) => payment
|
(payment) => payment
|
||||||
);
|
);
|
||||||
export const selectIsDiscount = createSelector(
|
|
||||||
(state: { payment: IPayment }) => state.payment.isDiscount,
|
|
||||||
(payment) => payment
|
|
||||||
);
|
|
||||||
export const selectSubscriptionReceipt = createSelector(
|
|
||||||
(state: { payment: IPayment }) => state.payment.subscriptionReceipt,
|
|
||||||
(payment) => payment
|
|
||||||
);
|
|
||||||
export const selectStripeButton = createSelector(
|
|
||||||
(state: { payment: IPayment }) => state.payment.stripeButton,
|
|
||||||
(payment) => payment
|
|
||||||
);
|
|
||||||
export default paymentSlice.reducer;
|
export default paymentSlice.reducer;
|
||||||
|
|||||||
@ -25,6 +25,8 @@ const initialState: TPaywalls = {
|
|||||||
"aura.placement.palmistry.main": null,
|
"aura.placement.palmistry.main": null,
|
||||||
"aura.placement.palmistry.redesign": null,
|
"aura.placement.palmistry.redesign": null,
|
||||||
"aura.placement.chat": null,
|
"aura.placement.chat": null,
|
||||||
|
"aura.placement.email.palmistry": null,
|
||||||
|
"aura.placement.email.palmistry.discount": null,
|
||||||
isMustUpdate: {
|
isMustUpdate: {
|
||||||
"aura.placement.v1.mike": true,
|
"aura.placement.v1.mike": true,
|
||||||
"aura.placement.main": true,
|
"aura.placement.main": true,
|
||||||
@ -34,6 +36,8 @@ const initialState: TPaywalls = {
|
|||||||
"aura.placement.palmistry.main": true,
|
"aura.placement.palmistry.main": true,
|
||||||
"aura.placement.palmistry.redesign": true,
|
"aura.placement.palmistry.redesign": true,
|
||||||
"aura.placement.chat": true,
|
"aura.placement.chat": true,
|
||||||
|
"aura.placement.email.palmistry": true,
|
||||||
|
"aura.placement.email.palmistry.discount": true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
import {
|
|
||||||
createSlice, createEntityAdapter, createSelector, EntityState
|
|
||||||
} from '@reduxjs/toolkit'
|
|
||||||
import { SubscriptionItems } from '../api'
|
|
||||||
|
|
||||||
type SubscriptionPlan = SubscriptionItems.ItemPrice
|
|
||||||
|
|
||||||
const subscriptionPlanAdapter = createEntityAdapter<SubscriptionPlan>({
|
|
||||||
selectId: (plan) => plan.id,
|
|
||||||
sortComparer: (a, b) => a.created_at - b.created_at,
|
|
||||||
})
|
|
||||||
|
|
||||||
const initialState = subscriptionPlanAdapter.getInitialState()
|
|
||||||
|
|
||||||
const subscriptionPlanSlice = createSlice({
|
|
||||||
name: 'subscriptionPlans',
|
|
||||||
initialState,
|
|
||||||
reducers: {
|
|
||||||
setAll: subscriptionPlanAdapter.setAll,
|
|
||||||
},
|
|
||||||
extraReducers: (builder) => builder.addCase('reset', () => initialState)
|
|
||||||
})
|
|
||||||
|
|
||||||
export const { actions } = subscriptionPlanSlice
|
|
||||||
const { selectById } = subscriptionPlanAdapter.getSelectors()
|
|
||||||
export const selectPlanById = (id: string) => createSelector(
|
|
||||||
(state: { subscriptionPlans: EntityState<SubscriptionPlan> }) => state.subscriptionPlans,
|
|
||||||
(state) => selectById(state, id)
|
|
||||||
)
|
|
||||||
export default subscriptionPlanSlice.reducer
|
|
||||||
Loading…
Reference in New Issue
Block a user