Merge branch 'develop' into 'main'

Develop

See merge request witapp/aura-webapp!280
This commit is contained in:
Daniil Chemerkin 2024-07-31 21:56:08 +00:00
commit 0b1ddb01ac
75 changed files with 2683 additions and 8 deletions

BIN
public/bg-gradient.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 247 KiB

BIN
public/bg-location-map.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 595 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 139 KiB

BIN
public/mike/assistant_3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

BIN
public/mike/avatar.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

BIN
public/mike/avatar_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

BIN
public/mike/avatar_3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 213 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 216 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 184 KiB

BIN
public/mike/chat1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

BIN
public/mike/chat2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

BIN
public/mike/female.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
public/mike/male.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

BIN
public/mike/user1.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/mike/user2.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/mike/user3.webp Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

View File

@ -10,6 +10,7 @@ export interface PayloadGet extends Payload {
}
export enum EPlacementKeys {
"aura.placement.v1.mike" = "aura.placement.v1.mike",
"aura.placement.main" = "aura.placement.main",
"aura.placement.redesign.main" = "aura.placement.redesign.main",
"aura.placement.email.marketing" = "aura.placement.email.marketing",

View File

@ -121,6 +121,7 @@ import LoadingPage from "../pages/LoadingPage";
import { EProductKeys, productUrls } from "@/data/products";
import SinglePaymentPage from "../pages/SinglePaymentPage";
import ABDesignV1Routes from "@/routerComponents/ABDesign/v1";
import MikeV1Routes from "@/routerComponents/Mike/v1";
import metricService from "@/services/metric/metricService";
const isProduction = import.meta.env.MODE === "production";
@ -285,6 +286,7 @@ function App(): JSX.Element {
<Route element={<AuthorizedUserOutlet />}>
<Route path="*" element={<ABDesignV1Routes />} />
</Route>
<Route path={`${routes.client.mikeV1()}/*`} element={<MikeV1Routes />} />
<Route element={<Layout setIsSpecialOfferOpen={setIsSpecialOfferOpen} />}>
<Route path={routes.client.loadingPage()} element={<LoadingPage />} />
{/* Email - Pay - Email */}
@ -1064,9 +1066,32 @@ interface IShortPathOutletProps {
}
function ShortPathOutlet(props: IShortPathOutletProps): JSX.Element {
const dispatch = useDispatch();
const { productKey, requiredParameters, redirectUrls, isProductPage } = props;
const { user, token } = useAuth();
const dateOfPaymentChatMike = useSelector(
selectors.selectDateOfPaymentChatMike
);
const queryParameters = new URLSearchParams(window.location.search);
const paymentMadeChatMike = queryParameters.get("paymentMadeChatMike");
if (paymentMadeChatMike && !dateOfPaymentChatMike) {
dispatch(actions.userConfig.setDateOfPaymentChatMike(new Date()));
}
const isForcePaymentStatus = useMemo(() => {
if ((Date.now() - new Date(dateOfPaymentChatMike).getTime()) / 1000 < 180) {
return true;
}
if (paymentMadeChatMike && !dateOfPaymentChatMike) {
return true;
}
return false;
}, [dateOfPaymentChatMike, paymentMadeChatMike]);
const api = useApi();
const isForce = useSelector(selectors.selectIsForceShortPath);
@ -1122,7 +1147,7 @@ function ShortPathOutlet(props: IShortPathOutletProps): JSX.Element {
}
return <Outlet />;
}
if (!isPurchasedProduct) {
if (!isPurchasedProduct && !isForcePaymentStatus) {
if (isForce && redirectUrls.purchasedProduct?.force) {
return (
<Navigate to={redirectUrls.purchasedProduct.force} replace={true} />

View File

@ -8,8 +8,12 @@ const isValidEmail = (email: string) => {
return re.test(String(email).toLowerCase().trim());
};
function EmailInput(props: FormField<string>): JSX.Element {
const { name, value, placeholder, onValid, onInvalid } = props;
type EmailInputProps = FormField<string> & {
className?: string;
};
function EmailInput(props: EmailInputProps): JSX.Element {
const { name, value, placeholder, onValid, onInvalid, className } = props;
const [email, setEmail] = useState(value);
const handleChangeEmail = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -35,6 +39,7 @@ function EmailInput(props: FormField<string>): JSX.Element {
<input
type="email"
name={name}
className={className}
id="email"
value={email}
onChange={handleChangeEmail}

View File

@ -1,4 +1,3 @@
.page-footer {
font-size: 14px;
font-weight: 400;
@ -9,10 +8,8 @@
}
.page-footer--white {
background-color: #fff;
}
.page-footer--black {
background-color: #04040a;
color: #fff;
}

View File

@ -8,6 +8,7 @@ interface IMessageProps {
avatar?: string;
backgroundTextColor?: string;
textColor?: string;
isVisible?: boolean;
}
// eslint-disable-next-line react-refresh/only-export-components
@ -18,7 +19,9 @@ function Message({
advisorName = "Advisor",
backgroundTextColor = "#0080ff",
textColor = "#fff",
isVisible = true,
}: IMessageProps) {
if (!isVisible) return null;
return (
<div
className={styles.container}

View File

@ -325,6 +325,19 @@ function AdvisorChatPage() {
return messageText;
};
useEffect(() => {
(async () => {
if (
assistant &&
!!assistant?.external_id &&
!assistant?.external_chat_id?.length
) {
await sendMessage("HI");
}
})();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [assistant?.external_id]);
return (
<section className={`${styles.page} page`}>
{isLoading && (
@ -355,6 +368,7 @@ function AdvisorChatPage() {
<Message
avatar={assistant?.photo?.th2x || ""}
text={deleteDataFromMessage(content.text.value)}
isVisible={deleteDataFromMessage(content.text.value) !== " HI"}
advisorName={assistant?.name || ""}
backgroundTextColor={
getIsSelfMessage(message.role) ? "#0080ff" : "#c9c9c9"

View File

@ -0,0 +1,36 @@
import styles from "./styles.module.css";
import { useNavigate } from "react-router-dom";
import cn from "classnames";
interface IButtonBack {
theme?: "light" | "dark";
}
function ButtonBack({ theme = "dark" }: IButtonBack) {
const navigate = useNavigate();
const goBack = () => {
return navigate(-1);
};
return (
<button
className={cn(styles["back__button"], styles[theme])}
onClick={goBack}
>
<svg
width="11"
height="20"
viewBox="0 0 11 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M2.43074 10.001L10.7039 1.72301C11.0987 1.32834 11.0987 0.689919 10.7039 0.295252C10.309 -0.0984174 9.66986 -0.0984174 9.27603 0.295252L0.288927 9.28662C-0.099868 9.67431 -0.0927333 10.3318 0.288927 10.7134L9.27603 19.7047C9.67086 20.0984 10.31 20.0984 10.7039 19.7047C11.0977 19.3111 11.0987 18.6717 10.7039 18.278L2.43074 10.001Z"
fill="currentColor"
/>
</svg>
</button>
);
}
export default ButtonBack;

View File

@ -0,0 +1,24 @@
.back__button {
width: 20px;
height: 30px;
position: absolute;
top: 35px;
left: 17px;
padding: 5px;
cursor: pointer;
outline: none;
background-color: transparent;
background-image: none;
outline: none;
line-height: inherit;
color: inherit;
text-transform: none;
border: 0px;
z-index: 10;
}
.light {
color: #fff
}
.dark {
color: #000
}

View File

@ -0,0 +1,21 @@
import { ReactNode } from "react";
import styles from "./styles.module.css";
import { Link as LinkRouter } from "react-router-dom";
function Link({
to,
children,
className = "",
}: {
to: string;
children?: ReactNode;
className?: string;
}) {
return (
<LinkRouter to={to} className={`${styles.link} ${className}`}>
{children}
</LinkRouter>
);
}
export default Link;

View File

@ -0,0 +1,7 @@
.link {
margin-block: 0 10px;
margin-inline: auto;
text-align: center;
text-decoration: underline;
text-underline-offset: 3px;
}

View File

@ -0,0 +1,19 @@
import styles from "./styles.module.css";
import Title from "@/components/Title";
import { FC, PropsWithChildren } from "react";
import cn from "classnames";
type MessageProps = {
className?: string;
};
export const Message: FC<PropsWithChildren<MessageProps>> = ({
children,
className,
}) => {
return (
<Title variant="h2" className={cn(styles["message"], className)}>
{children}
</Title>
);
};

View File

@ -0,0 +1,36 @@
.message {
position: relative;
margin-block: 30px 16px;
padding: 10px 20px;
line-height: 1.2;
border-radius: 16px;
background: #fff;
text-align: left;
z-index: 1;
}
.message::after {
content: "";
position: absolute;
display: block;
top: -18px;
left: 50%;
width: 50px;
height: 50px;
background: #fff;
rotate: 45deg;
translate: -50% 0;
z-index: -1;
}
@media (max-width: 420px) {
.message {
margin-block: 24px 10px;
padding: 6px 14px;
font-size: 21px;
}
.message::after {
top: -16px;
width: 40px;
height: 40px;
}
}

View File

@ -0,0 +1,44 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
interface IQuestionProps {
title: string;
text: string;
}
function Question({ title, text }: IQuestionProps) {
return (
<div className={styles.question}>
<div className={styles["image-container"]}>
<svg
width="12"
height="20"
viewBox="0 0 12 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M5.43681 19.5419C6.23411 19.5419 6.88045 18.8955 6.88045 18.0982C6.88045 17.3009 6.23411 16.6546 5.43681 16.6546C4.63951 16.6546 3.99316 17.3009 3.99316 18.0982C3.99316 18.8955 4.63951 19.5419 5.43681 19.5419Z"
fill="#353E75"
/>
<path
d="M1.10498 5.10585C1.10498 5.10585 1.18077 3.31942 2.87029 1.88704C3.87347 1.03659 5.07823 0.790283 6.15773 0.775126C7.14355 0.762675 8.02462 0.93428 8.55148 1.19845C9.45203 1.6521 11.2105 2.75643 11.2105 5.10585C11.2105 7.57815 9.67466 8.69872 7.92883 9.93352C6.183 11.1683 5.73667 12.3793 5.73667 13.7673"
stroke="#353E75"
strokeWidth="1.29364"
strokeMiterlimit="10"
strokeLinecap="round"
/>
</svg>
</div>
<div>
<Title variant="h5" className={styles["secondary-title"]}>
{title}
</Title>
<p className={styles.text}>{text}</p>
</div>
</div>
);
}
export default Question;

View File

@ -0,0 +1,31 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import Question from "./Question";
import { questionsMike } from "@/data/oftenAsk";
import { ReactNode } from "react";
interface IOftenAsk {
title?: ReactNode | string;
}
function OftenAsk({ title }: IOftenAsk) {
return (
<>
{typeof title === "string" ? (
<Title variant="h2" className={styles.title}>
{title}
</Title>
) : (
title
)}
<ul className={styles.questions}>
{questionsMike.map((question, index) => (
<li key={index}>
<Question {...question} />
</li>
))}
</ul>
</>
);
}
export default OftenAsk;

View File

@ -0,0 +1,45 @@
.title {
margin-bottom: 20px;
font-size: 24px;
line-height: 145%;
text-align: center;
color: #333333;
font-weight: 700;
}
.questions {
display: flex;
flex-direction: column;
gap: 26px;
}
.question {
display: flex;
gap: 8px;
}
.image-container {
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
height: 30px;
width: 30px;
border-radius: 50%;
background-color: #fff;
}
.secondary-title {
flex: 1 1 0%;
font-weight: 600;
font-size: 16px;
line-height: 125%;
margin: 0;
text-align: left;
}
.text {
font-weight: 400;
font-size: 16px;
line-height: 140%;
margin-top: 6px;
}

View File

@ -0,0 +1,99 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { ReactNode, useState } from "react";
import FullScreenModal from "@/components/FullScreenModal";
import { IPaywallProduct } from "@/api/resources/Paywall";
import { Button } from "../../ui/Button";
import GuardPayments from "@/components/pages/ABDesign/v1/pages/TrialPayment/components/GuardPayments";
import { useGenderInfo } from "../../lib/getGenderInfo";
interface IPaymentTableProps {
product: IPaywallProduct;
buttonClick: () => void;
title?: ReactNode | string;
}
const getPrice = (product: IPaywallProduct) => {
return (product.trialPrice || 0) / 100;
};
function PaymentTable({
product,
buttonClick,
title = "Special offer",
}: IPaymentTableProps) {
const [isOpenPrivacyModal, setIsOpenPrivacyModal] = useState<boolean>(false);
const genderInfo = useGenderInfo();
const handleSubscriptionPolicyClick = (event: React.MouseEvent) => {
event.preventDefault();
setIsOpenPrivacyModal(true);
};
return (
<>
{isOpenPrivacyModal && (
<FullScreenModal isOpen={isOpenPrivacyModal}>
<div className={styles["modal-container"]}>
<img
className={styles.cross}
src="/cross.webp"
alt="Cross"
onClick={() => setIsOpenPrivacyModal(false)}
/>
<iframe
className={styles["iframe"]}
src="https://witapps.us/en/subscription-terms"
></iframe>
</div>
</FullScreenModal>
)}
<div className={styles["payment-table"]}>
<div className={styles.header}>
{typeof title === "string" ? (
<Title variant="h2" className={styles.title}>
{title}
</Title>
) : (
title
)}
</div>
<div className={styles["table-container"]}>
<Title variant="h3" className={styles.title}>
Personalized reading for ${getPrice(product)}
</Title>
<div className={styles["table-element"]}>
<p>Total today:</p>
<span>${getPrice(product)}</span>
</div>
<hr />
<div className={styles["table-footer"]}>
<p>Your cost per 2 weeks after trial</p>
<div>
<span className={styles.discount}>$65</span>
<span className={styles.price}>${product.trialPrice / 100}</span>
</div>
</div>
</div>
</div>
<Button
className={styles.button}
style={{ backgroundColor: genderInfo?.colorAssociation }}
onClick={buttonClick}
>
START CHAT
</Button>
<GuardPayments />
<p className={styles.policy}>
You are enrolling in 2 weeks subscription. By continuing you agree that
if you don't cancel prior to the end of the 3-day trial for the $
{getPrice(product)} you will automatically be charged $19 every 2 weeks
until you cancel in settings. Learn more about cancellation and refund
policy in{" "}
<a onClick={handleSubscriptionPolicyClick}>Subscription policy</a>
</p>
</>
);
}
export default PaymentTable;

View File

@ -0,0 +1,129 @@
.payment-table {
width: 100%;
margin-top: 50px;
}
.header {
margin-bottom: 18px;
}
.header .title {
margin-bottom: 18px;
font-size: 47px;
font-weight: 800;
text-align: center;
}
.table-container {
padding: 13px;
margin-inline: 26px;
border-radius: 10px;
background: #fff;
}
.table-container > hr {
margin: 10px 0;
}
.title {
margin-bottom: 18px;
font-weight: 700;
font-size: 15px;
color: #0f0f0f;
}
.table-element,
.table-footer {
display: flex;
-webkit-box-pack: justify;
justify-content: space-between;
-webkit-box-align: center;
align-items: center;
}
.table-element > p {
font-weight: 500;
font-size: 16px;
line-height: 24px;
color: #2c2c2c;
}
.table-element > span {
font-weight: 800;
font-size: 19px;
line-height: 22px;
margin-left: 4px;
color: #2c2c2c;
}
.table-footer > p,
.table-footer span {
font-size: 12px;
}
.button {
height: 53px;
max-width: 360px;
width: 100%;
margin-top: 21px;
border-radius: 8px;
}
.policy {
max-width: 336px;
font-size: 12px;
line-height: 20px;
font-weight: 300;
margin-top: 18px;
margin-inline: auto;
}
.policy > a {
text-decoration: underline;
font-weight: 600;
cursor: pointer;
}
.discount {
text-decoration: line-through;
align-self: flex-end;
font-weight: 400;
font-size: 12px;
line-height: 22px;
color: rgba(44, 44, 44, 0.6);
margin-right: 2px;
}
.price {
font-size: 14px;
}
.modal-container,
.iframe {
position: relative;
width: 100%;
height: 100%;
}
.iframe {
pointer-events: all;
}
.cross {
position: fixed;
top: 60px;
right: 20px;
width: 36px;
height: 36px;
padding: 8px;
border-radius: 100%;
background-color: #fff;
z-index: 10;
cursor: pointer;
}
@media (max-width: 430px) {
.header .title {
font-size: 38px;
}
}

View File

@ -0,0 +1,54 @@
import styles from "./styles.module.css";
interface IPersonalInformationProps {
birthdate: string;
zodiacSign: string;
gender: string;
birthPlace: string;
primaryColor?: string;
}
function PersonalInformation({
birthdate,
zodiacSign,
gender,
birthPlace,
primaryColor,
}: IPersonalInformationProps) {
return (
<div
className={styles["personal-information"]}
>
<div className={styles["image-container"]}>
<img
src={`/questionnaire-redesign/zodiacs/${gender}/pdf.sex.${zodiacSign?.toUpperCase()}.${gender.toUpperCase()}.webp`}
alt={`${gender} ${zodiacSign}`}
/>
</div>
<div className={styles["text-information"]}>
<ul>
<li>
<h6 style={{color: primaryColor}}>Zodiac sign</h6>
<p>{zodiacSign}</p>
</li>
<li>
<h6 style={{color: primaryColor}}>Gender</h6>
<p>{gender}</p>
</li>
</ul>
<ul>
<li>
<h6 style={{color: primaryColor}}>Date of birth</h6>
<p>{birthdate}</p>
</li>
<li>
<h6 style={{color: primaryColor}}>Place of birth</h6>
<p>{birthPlace}</p>
</li>
</ul>
</div>
</div>
);
}
export default PersonalInformation;

View File

@ -0,0 +1,62 @@
.personal-information {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 22px;
}
.personal-information > div {
width: 100%;
background: #fff;
border-radius: 10px;
box-shadow: 0px 0px 14px rgba(0,0,0,.25);
}
.image-container {
padding: 8px;
}
.image-container > img {
display: block;
height: 278px;
margin-inline: auto;
}
.text-information {
background: #fbfbff;
border-radius: 0px 0px 20px 20px;
width: 100%;
display: flex;
justify-content: space-between;
position: relative;
padding: 12px 48px;
}
.text-information > ul {
width: min-content;
display: flex;
flex-wrap: wrap;
gap: 8px;
position: relative;
text-wrap: nowrap;
}
.text-information > ul > li {
width: 100%;
}
.text-information > ul > li > h6 {
font-weight: 500;
font-size: 15px;
line-height: 125%;
}
.text-information > ul > li > p {
font-size: 13px;
line-height: 130%;
margin-top: 4px;
text-transform: capitalize;
color: #0f0f0f;
}

View File

@ -0,0 +1,46 @@
import { ReactNode } from "react";
import styles from "./styles.module.css";
interface IPointsListProps {
points: string[];
title?: ReactNode;
containerClassName?: string;
}
function PointsList({
points,
title,
containerClassName = "",
}: IPointsListProps) {
return (
<div className={`${styles["you-get"]} ${containerClassName}`}>
{title}
<ul>
{points.map((point, index) => (
<li key={index}>
<svg
className={styles.checkIcon}
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.4375 1.98267L4.83125 11.8465L2 8.14756"
stroke="#3750A8"
strokeWidth="2.09203"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
{point}
</li>
))}
</ul>
</div>
);
}
export default PointsList;

View File

@ -0,0 +1,16 @@
.you-get {
max-width: 320px;
}
.you-get > ul > li {
display: flex;
gap: 6px;
font-size: 16px;
line-height: 140%;
margin-bottom: 4px;
}
.checkIcon {
margin-top: 3px;
flex-shrink: 0;
}

View File

@ -0,0 +1,45 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { IReview } from "@/data/reviews";
type IReviewProps = IReview;
function Review({ username, date, text, mark, image }: IReviewProps) {
return (
<div className={styles.review}>
<div className={`${styles.avatar} ${image?.length && styles.middle}`}>
{!image?.length && <span>{username.slice(0, 2)}</span>}
{!!image?.length && (
<img className={styles.image} src={image} alt="Customer Avatar" />
)}
</div>
<div>
<div className={styles.header}>
<div className={styles.info}>
<Title variant="h6" className={styles.title}>
{username}
</Title>
<p>{date}</p>
</div>
{!!mark && (
<span
className={styles.stars}
style={{
background: `linear-gradient(
90deg,
rgb(255, 204, 0) ${(mark / 5) * 100}%,
rgb(238, 238, 238) 0px
)`,
}}
></span>
)}
</div>
<div className={styles.content}>
<p className={styles.text}>{text}</p>
</div>
</div>
</div>
);
}
export default Review;

View File

@ -0,0 +1,23 @@
import styles from "./styles.module.css";
import { IReview } from "@/data/reviews";
import Review from "./Review";
interface IReviewsProps {
reviews: IReview[];
}
function Reviews({ reviews }: IReviewsProps) {
return (
<div className={styles.reviews}>
<ul>
{reviews.map((review, index) => (
<li key={index}>
<Review {...review} />
</li>
))}
</ul>
</div>
);
}
export default Reviews;

View File

@ -0,0 +1,111 @@
.reviews > ul {
display: flex;
flex-direction: column;
justify-content: center;
gap: 14px;
}
.reviews > ul > li {
width: 100%;
}
.review {
display: grid;
grid-template-columns: min(60px, 16%) 1fr;
gap: 8px 12px;
align-items: start;
height: 100%;
padding: 13px;
background: rgb(255, 255, 255);
border-radius: 10px;
box-shadow: 1px 2px 5px rgba(0, 0, 0, 0.25);
}
.header {
display: flex;
}
.avatar {
width: 32px;
height: 32px;
background-color: rgb(210, 209, 249);
border-radius: 50%;
display: flex;
-webkit-box-align: center;
align-items: center;
-webkit-box-pack: center;
justify-content: center;
}
.avatar.middle {
width: 100%;
height: auto;
}
.avatar > span {
font-weight: 600;
font-size: 16px;
line-height: 24px;
background: linear-gradient(
rgb(20, 19, 51) 0%,
rgb(32, 34, 97) 70.63%,
rgb(58, 35, 122) 100%
)
text;
-webkit-text-fill-color: transparent;
}
.info {
flex: 1 1 0%;
margin-left: 6px;
}
.info > .title {
font-weight: 600;
font-size: 16px;
line-height: 130%;
color: #000;
margin: 0;
text-align: left;
}
.info > p {
font-size: 14px;
line-height: 120%;
margin-top: 2px;
color: #000;
}
.stars {
max-width: 6.75rem;
font-size: 1.375rem;
height: 1em;
width: 100%;
mask-image: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTciIGhlaWdodD0iMTYiIHZpZXdCb3g9IjAgMCAxNyAxNiIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPGcgY2xpcC1wYXRoPSJ1cmwoI2NsaXAwKSI+CjxwYXRoIGQ9Ik04LjQ5OTc4IDEyLjE3NEwzLjc5Nzc4IDE0LjgwNkw0Ljg0Nzc4IDkuNTIwNjVMMC44OTExMTMgNS44NjE5OEw2LjI0MjQ1IDUuMjI3MzJMOC40OTk3OCAwLjMzMzk4NEwxMC43NTcxIDUuMjI3MzJMMTYuMTA4NCA1Ljg2MTk4TDEyLjE1MTggOS41MjA2NUwxMy4yMDE4IDE0LjgwNkw4LjQ5OTc4IDEyLjE3NFoiIGZpbGw9IiNGMkM5NEMiLz4KPC9nPgo8ZGVmcz4KPGNsaXBQYXRoIGlkPSJjbGlwMCI+CjxyZWN0IHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgZmlsbD0id2hpdGUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAuNSkiLz4KPC9jbGlwUGF0aD4KPC9kZWZzPgo8L3N2Zz4K);
mask-position: 0px 0px;
mask-size: 1em 1em;
}
.content {
display: flex;
margin-top: 10px;
}
.opostrafs {
width: 32px;
}
.text {
flex: 1 1 0%;
font-size: 16px;
line-height: 1.25;
font-weight: 300;
margin-left: 6px;
color: rg#0f0f0f;
}
.image {
width: 100%;
height: 100%;
object-fit: cover;
}

View File

@ -0,0 +1,34 @@
import styles from "./styles.module.css";
import DiscountExpires from "@/components/pages/TrialPayment/components/DiscountExpires";
import CustomButton from "@/components/pages/TrialPayment/components/CustomButton";
import { useGenderInfo } from "../../lib/getGenderInfo";
interface ITrialPaymentHeaderProps {
buttonText?: string;
buttonClassName?: string;
buttonClick: () => void;
}
function TrialPaymentHeader({
buttonClick,
buttonText = "get my reading",
buttonClassName = "",
}: ITrialPaymentHeaderProps) {
const genderInfo = useGenderInfo();
return (
<header className={styles.header}>
<DiscountExpires />
<CustomButton
className={`${styles.button} ${buttonClassName}`}
style={{
backgroundColor: genderInfo?.colorAssociation,
}}
onClick={buttonClick}
>
{buttonText}
</CustomButton>
</header>
);
}
export default TrialPaymentHeader;

View File

@ -0,0 +1,26 @@
.header {
position: sticky;
top: -14px;
z-index: 30;
width: calc(100% + 36px);
display: flex;
-webkit-box-align: center;
align-items: center;
gap: 8px;
justify-content: space-between;
padding-inline: 12px;
margin-top: -36px;
/* background: rgba(240, 240, 240, .6); */
backdrop-filter: blur(8px);
}
.header > div {
flex-shrink: 0;
}
.button {
height: 45px;
font-size: 25px;
font-weight: 600;
text-transform: none;
}

View File

@ -0,0 +1,26 @@
export interface Gender {
id: string;
name: string;
img: string;
colorAssociation: string;
subColorAssociation?: string;
bgColor: string;
}
export const genders: Gender[] = [
{
id: "male",
name: "Male",
img: "/mike/male.webp",
colorAssociation: "#3390EC",
subColorAssociation: '#353E75',
bgColor:"#E2F0FE",
},
{
id: "female",
name: "Female",
img: "/mike/female.webp",
colorAssociation: "#F36CFF",
bgColor: "#FFEEF9",
},
];

View File

@ -0,0 +1,10 @@
import { useSelector } from "react-redux";
import { Gender, genders } from "../data/data";
import { selectors } from "@/store";
export function useGenderInfo(): Gender | undefined {
const currentGender = useSelector(selectors.selectQuestionnaire).gender;
const infoGender = genders.find((g) => g.id === currentGender);
return infoGender;
}

View File

@ -0,0 +1,99 @@
import styles from "./styles.module.css";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import routes from "@/routes";
import PlacePicker from "@/components/PlacePicker";
import { useEffect, useState } from "react";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { Button } from "../../ui/Button";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
import { useGenderInfo } from "../../lib/getGenderInfo";
import ButtonBack from "../../components/ButtonBack";
import { Message } from "../../components/Message";
import { Card } from "../../ui/Card";
interface IBirthPlaceProps {
affiliation?: "self" | "partner";
}
function BirthPlacePage({ affiliation = "self" }: IBirthPlaceProps) {
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const questionnaire = useSelector(selectors.selectQuestionnaire);
const birthPlace =
affiliation === "self"
? questionnaire.birthPlace
: questionnaire.partnerBirthPlace;
const [isDisabled, setIsDisabled] = useState(() => !birthPlace);
const { setStyle } = useStyle();
const genderInfo = useGenderInfo();
useLottie({
preloadKey: ELottieKeys.magnifyingGlassAndPlanet,
});
const handleChange = (birthPlace: string) => {
if (affiliation === "partner") {
return dispatch(
actions.questionnaire.update({ partnerBirthPlace: birthPlace })
);
}
if (birthPlace !== "") {
setIsDisabled(false);
} else {
setIsDisabled(true);
}
// affiliation === "self"
return dispatch(actions.questionnaire.update({ birthPlace }));
};
const handleNext = () => {
if (affiliation === "partner") {
return navigate(routes.client.relationshipAlmostThereV1());
}
// affiliation === "self"
return navigate(routes.client.mikeV1() + routes.client.emailEnter());
};
useEffect(() => {
setStyle({
backgroundImage: "url(/bg-location-map.webp)",
backgroundRepeat: "no-repeat",
backgroundSize: "cover",
});
}, [setStyle]);
return (
<div className="page-mike page--dark">
<ButtonBack theme="light" />
<div className={styles.assistant}>
<img src="/mike/avatar_2.png" />
<Message>{t("mike.birthplace.message")}</Message>
</div>
<Card className={styles.card}>
<PlacePicker
value={birthPlace}
name="birthPlace"
maxLength={1000}
classNameInput={styles["full-address"]}
onChange={handleChange}
/>
<Button
className={styles.button}
style={{ background: genderInfo?.colorAssociation }}
onClick={handleNext}
disabled={isDisabled}
>
{t("next")}
</Button>
</Card>
</div>
);
}
export default BirthPlacePage;

View File

@ -0,0 +1,45 @@
:global(.page--dark + .page-footer) {
color: #fff;
}
.assistant {
display: flex;
flex-direction: column;
align-items: center;
margin-top: 36px;
padding-inline: 10px;
}
.assistant img {
display: block;
margin-bottom: 24px;
border-radius: 50%;
width: 22.5%;
}
.card {
margin-top: 16px;
padding: 40px 34px;
background: rgba(255, 255, 255, 0.78);
}
.label {
color: #6b7baa;
font-size: 12px;
line-height: 16px;
margin: 0 0 6px 6px;
}
.full-address {
display: block;
width: 100%;
max-width: 326px;
margin-inline: auto;
border-radius: 6px;
margin-bottom: 24px;
background: #fff;
text-align: center;
border-radius: 50px;
font-size: 20px;
padding: 14px 17px;
}

View File

@ -0,0 +1,68 @@
import styles from "./styles.module.css";
import { TimePicker } from "@/components/DateTimePicker";
import routes from "@/routes";
import { actions, selectors } from "@/store";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import ButtonBack from "../../components/ButtonBack";
import { Message } from "../../components/Message";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
import { useEffect } from "react";
import { useGenderInfo } from "../../lib/getGenderInfo";
import { Card } from "../../ui/Card";
import { Button } from "../../ui/Button";
import Link from "../../components/Link";
function BirthTimePage() {
const { t } = useTranslation();
const dispatch = useDispatch();
const navigate = useNavigate();
const birthtime = useSelector(selectors.selectBirthtime);
const { setStyle } = useStyle();
const genderInfo = useGenderInfo();
let nextRoute = routes.client.mikeV1() + routes.client.getBirthPlace();
if (window.location.href.includes("/advisor-chat/")) {
nextRoute = routes.client.advisorChatBirthPlace();
}
const handleNext = () => navigate(nextRoute);
const handleChange = (value: string) => dispatch(actions.form.addTime(value));
useEffect(() => {
setStyle({
backgroundImage: "url(/bg-gradient.png)",
backgroundPosition: "left bottom",
backgroundRepeat: "no-repeat",
});
}, [setStyle]);
return (
<>
<ButtonBack theme="light" />
<div className="page-mike">
<div className={styles.assistant}>
<figure className={styles["assistant-img"]}>
<img src="/mike/assistant_2.webp" />
</figure>
<Message className={styles["assistant-message"]}>
{t("mike.birthtime.message")}
</Message>
</div>
<Card className={styles.card}>
<p className="description">{t("mike.birthtime.description")}</p>
<TimePicker value={birthtime} onChange={handleChange} />
<Button
onClick={handleNext}
style={{ background: genderInfo?.colorAssociation }}
className={styles.button}
>
{t("next")}
</Button>
</Card>
<Link to={nextRoute}>Skip this step and continue further</Link>
</div>
</>
);
}
export default BirthTimePage;

View File

@ -0,0 +1,41 @@
.assistant {
position: relative;
width: calc(100% + 28px);
margin-top: -14px;
background: transparent;
z-index: 1;
}
.assistant-img {
position: relative;
z-index: -1;
}
.assistant-img img {
position: relative;
display: block;
width: 100%;
height: 100%;
object-fit: cover;
z-index: -1;
}
.assistant-message {
position: absolute;
left: 18px;
right: 18px;
bottom: 56px;
}
.card {
z-index: 1;
margin-top: -47px;
margin-bottom: 26px;
}
.button {
margin-top: 18px;
}
@media (max-width: 420px) {
.assistant-message {
bottom: 40px;
}
.card {
margin-top: -42px;
}
}

View File

@ -0,0 +1,231 @@
import styles from "./styles.module.css";
import { useEffect, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import routes from "@/routes";
import Title from "@/components/Title";
import Policy from "@/components/Policy";
import Loader, { LoaderColor } from "@/components/Loader";
import { ESourceAuthorization } from "@/api/resources/User";
import { useAuthentication } from "@/hooks/authentication/use-authentication";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import metricService, { EGoals } from "@/services/metric/metricService";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import ButtonBack from "../../components/ButtonBack";
import { Button } from "../../ui/Button";
import { useGenderInfo } from "../../lib/getGenderInfo";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
import { Message } from "../../components/Message";
import EmailInput from "@/components/EmailEnterPage/EmailInput";
import NameInput from "@/components/EmailEnterPage/NameInput";
interface IEmailEnterPage {
redirectUrl?: string;
isRequiredName?: boolean;
}
function EmailEnterPage({
redirectUrl = routes.client.mikeV1() + routes.client.trialPayment(),
isRequiredName = false,
}: IEmailEnterPage): JSX.Element {
const { t } = useTranslation();
const dispatch = useDispatch();
const navigate = useNavigate();
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [isDisabled, setIsDisabled] = useState(true);
const [isValidEmail, setIsValidEmail] = useState(false);
const [isValidName, setIsValidName] = useState(!isRequiredName);
const [isAuth, setIsAuth] = useState(false);
const { subPlan } = useParams();
const { error, isLoading, token, user, authorization } = useAuthentication();
const { setStyle } = useStyle();
const infoGender = useGenderInfo();
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.v1.mike"],
});
const [activeProduct, setActiveProduct] = useState<IPaywallProduct | null>(
activeProductFromStore
);
useLottie({
preloadKey: ELottieKeys.handWithStars,
});
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);
}
}
}, [subPlan, products]);
const handleValidEmail = (email: string) => {
dispatch(actions.form.addEmail(email));
setEmail(email);
setIsValidEmail(true);
};
const handleValidName = (name: string) => {
if (name) {
dispatch(
actions.user.update({
username: name,
})
);
}
setName(name);
setIsValidName(true);
};
useEffect(() => {
if (isValidName && isValidEmail) {
setIsDisabled(false);
} else {
setIsDisabled(true);
}
}, [isValidEmail, isValidName, email, name]);
const handleClick = () => {
authorize();
metricService.reachGoal(EGoals.ENTERED_EMAIL);
};
const authorize = async () => {
let source = ESourceAuthorization["aura.main.new"];
if (window.location.pathname.includes("advisor-chat")) {
source = ESourceAuthorization["aura.chat"];
}
if (window.location.pathname.includes("/epe/")) {
source = ESourceAuthorization["aura.moons"];
}
await authorization(email, source);
};
useEffect(() => {
if (user && token?.length && !isLoading && !error) {
dispatch(
actions.payment.update({
activeProduct,
})
);
setIsAuth(true);
const timeout = setTimeout(() => {
navigate(redirectUrl);
}, 1000);
return () => {
clearTimeout(timeout);
};
}
}, [
activeProduct,
dispatch,
error,
isLoading,
navigate,
redirectUrl,
token?.length,
user,
]);
useEffect(() => {
if (infoGender) {
setStyle({
backgroundColor: "#F7F7F7",
backgroundImage: `url(/mike/bg-gradient-${infoGender.id}.webp)`,
backgroundPosition: "bottom",
backgroundRepeat: "no-repeat",
});
}
}, [infoGender, setStyle]);
useEffect(() => {
dispatch(actions.privacyPolicy.updateChecked(true));
setActiveProduct(products[0]);
}, [activeProduct, dispatch, products]);
return (
<div className="page-mike">
<ButtonBack />
<div className={styles["avatar-content"]}>
<img src="/mike/avatar_3.webp" />
<Message>{t("mike.emailEnter.message")}</Message>
<p className={styles["not-share"]}>{t("we_dont_share")}</p>
</div>
<div className={styles.form}>
<EmailInput
name="email"
value={email}
placeholder={t("your_email")}
onValid={handleValidEmail}
onInvalid={() => setIsValidEmail(false)}
/>
<NameInput
value={name}
placeholder="Your name"
onValid={handleValidName}
onInvalid={() => setIsValidName(!isRequiredName)}
/>
<Button
className={styles.button}
onClick={handleClick}
disabled={isDisabled}
style={{ backgroundColor: infoGender?.colorAssociation }}
>
{isLoading && <Loader color={LoaderColor.White} />}
{!isLoading && !(!error?.length && !isLoading && isAuth) && t("next")}
{!error?.length && !isLoading && isAuth && (
<img
className={styles["success-icon"]}
src="/SuccessIcon-white.svg"
alt="Success Icon"
/>
)}
</Button>
<Policy sizing="large" className={styles.policy}>
{t("_continue_agree", {
eulaLink: (
<a
className={styles.link}
href="https://aura.wit.life/terms"
target="_blank"
rel="noopener noreferrer"
>
{t("eula")}
</a>
),
privacyLink: (
<a
className={styles.link}
href="https://aura.wit.life/privacy"
target="_blank"
rel="noopener noreferrer"
>
{t("privacy_policy")}
</a>
),
})}
</Policy>
{!!error?.length && (
<Title variant="h3" style={{ color: "red", margin: 0 }}>
Something went wrong
</Title>
)}
</div>
</div>
);
}
export default EmailEnterPage;

View File

@ -0,0 +1,73 @@
.avatar-content {
margin-top: 18px;
padding-inline: 6px;
display: flex;
flex-direction: column;
align-items: center;
}
.avatar-content img {
display: block;
width: 55.5%;
margin-bottom: 12px;
border-radius: 50%;
overflow: hidden;
box-shadow: 0px 0px 150px #3b3b3b;
}
.not-share {
font-size: 16px;
line-height: 125%;
text-align: center;
margin: 24px auto 8px;
color: #333;
}
.policy {
margin-inline: auto;
margin-top: 30px;
max-width: 300px;
}
.policy > p {
font-size: 18px;
line-height: 125%;
}
.policy .link {
color: #4200ff;
font-size: 18px;
text-underline-offset: 4px;
}
.success-icon {
height: 32px;
width: 32px;
object-fit: cover;
}
.form {
width: 100%;
}
.form > [class*="container"] {
margin-bottom: 16px;
}
.form input {
height: 58px;
border-radius: 14px;
}
.button {
margin-top: 30px;
}
@media (max-width: 420px) {
.not-share {
font-size: 15px;
}
.policy > p {
font-size: 17px;
}
}

View File

@ -0,0 +1,102 @@
import Title from "@/components/Title";
import styles from "./styles.module.css";
import { EProductKeys } from "@/data/products";
import routes from "@/routes";
import { actions } from "@/store";
import { Avatar } from "@mui/material";
import { FC, useEffect } from "react";
import { useDispatch } from "react-redux";
import { useNavigate } from "react-router-dom";
import { Gender, genders } from "../../data/data";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
interface IGenderPageProps {
productKey?: EProductKeys;
}
const GenderPage: FC<IGenderPageProps> = ({ productKey }) => {
const dispatch = useDispatch();
const navigate = useNavigate();
const { setStyle } = useStyle();
const selectGender = (gender: Gender) => {
dispatch(actions.questionnaire.update({ gender: gender.id }));
if (productKey === EProductKeys["moons.pdf.aura"]) {
return navigate(routes.client.epeBirthdate());
}
if (productKey === EProductKeys["chat.aura"]) {
return navigate(routes.client.advisorChatBirthdate());
}
navigate(`${routes.client.mikeV1()}/questionnaire/profile/flowChoice`);
};
useEffect(() => {
setStyle({ background: "#f7f7f7" });
}, [setStyle]);
return (
<div className="page-mike">
<div className={styles["inner-container"]}>
<Avatar
className={styles.avatar}
alt="avatar"
src="/mike/avatar.webp"
sx={{ width: 63, height: 63 }}
/>
<Title variant="h3" className={styles.title}>
Understand Yourself and Improve Relationships With Astrology
</Title>
</div>
<Title variant="h4" className={styles.subtitle}>
1-Minute Personal Assessment
</Title>
<div className={styles["genders-wrapper"]}>
<Title variant="h3" className={styles["title-select"]}>
Select your gender:
</Title>
<div className={styles["genders-container"]}>
{genders.map((gender, index) => (
<div
className={styles["gender"]}
key={index}
onClick={() => {
selectGender(gender);
}}
>
<div className={styles["gender-img-container"]}>
<img
src={gender.img}
className={styles["gender__img"]}
alt={gender.id}
/>
</div>
<button
className={styles["gender__button"]}
style={{ background: gender.colorAssociation }}
>
<span>{gender.name}</span>
</button>
</div>
))}
</div>
</div>
<div className={styles["container-policy"]}>
<p className={styles.text}>
By continuing, you agree to our{" "}
<a href="#" className={styles.underlined}>
EULA
</a>{" "}
and{" "}
<a href="#" className={styles.underlined}>
Privacy Notice
</a>
. Have a question? Reach our support team{" "}
<a href="#" className={styles.underlined}>
here
</a>
</p>
</div>
</div>
);
};
export default GenderPage;

View File

@ -0,0 +1,99 @@
.inner-container {
padding-inline: 8px;
}
.avatar {
margin-bottom: 14px;
}
.title {
padding: 24px 26px;
margin-bottom: 14px;
background: #fff;
text-align: left;
line-height: 1.2;
border-radius: 16px;
border: 1px solid #00000014;
}
.subtitle {
width: 100%;
margin-bottom: 30px;
font-weight: 500;
text-align: center;
}
.genders-wrapper {
width: 100%;
padding: 20px 8px 35px;
border-radius: 16px;
background: #fff;
border: 1px solid #0000000a;
}
.genders-container {
display: flex;
gap: 14px;
margin-top: 45px;
justify-content: space-between;
width: 100%;
}
.gender {
width: calc(50% - 7px);
}
.gender-img-container {
display: flex;
align-items: end;
justify-content: center;
width: 100%;
aspect-ratio: 1 / 1;
border-radius: 50%;
overflow: hidden;
}
.gender__img {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: contain;
}
.gender__button {
margin-top: 30px;
padding: 12px;
width: 100%;
border: 0;
border-radius: 50px;
font-weight: 500;
color: #fff;
text-align: center;
}
.container-policy {
max-width: 366px;
margin-block: 16px 32px ;
flex: 1 1;
display: flex;
align-items: end;
}
.text {
text-align: center;
color: #000;
font-size: 15px;
font-weight: 400;
line-height: 1.2;
}
.underlined {
text-decoration: underline;
}
@media (max-width: 420px) {
.title {
margin-bottom: 10px;
padding: 20px 22px;
font-size: 16px;
}
.subtitle {
margin-bottom: 20px;
font-size: 16px;
}
.genders-container {
margin-top: 25px;
}
}

View File

@ -0,0 +1,84 @@
import { DatePicker } from "@/components/DateTimePicker";
import styles from "./styles.module.css";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store";
import { useEffect, useState } from "react";
import routes from "@/routes";
import Title from "@/components/Title";
import ButtonBack from "../../components/ButtonBack";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
import { Message } from "../../components/Message";
import { useGenderInfo } from "../../lib/getGenderInfo";
import { Button } from "../../ui/Button";
import { Card } from "../../ui/Card";
function QuestionnairePage() {
const { t } = useTranslation();
const navigate = useNavigate();
const dispatch = useDispatch();
const selfBirthdate = useSelector(selectors.selectBirthdate);
const genderInfo = useGenderInfo();
const [birthdate, setBirthdate] = useState(selfBirthdate);
const [isDisabled, setIsDisabled] = useState(true);
const { setStyle } = useStyle();
const handleValid = (_birthdate: string) => {
dispatch(actions.form.addDate(_birthdate));
setBirthdate(_birthdate);
setIsDisabled(_birthdate === "");
};
const handleNext = () => {
navigate(`${routes.client.mikeV1()}${routes.client.birthtime()}`);
};
useEffect(() => {
if (genderInfo) {
const styleBg = genderInfo
? {
background: genderInfo.bgColor,
}
: undefined;
setStyle(styleBg);
}
}, [genderInfo, setStyle]);
return (
<>
<ButtonBack />
<div className="page-mike">
<div>
<div className={styles["img-wrapper"]}>
<img src="/mike/assistant_1.webp" />
</div>
<Message>{t("mike.questionnaire.message")}</Message>
</div>
<Card>
<Title variant="h4" className={styles.question}>
What's your date of birth?
</Title>
<DatePicker
name="birthdate"
value={birthdate}
onValid={handleValid}
onInvalid={() => setIsDisabled(true)}
inputClassName={`date-picker-input ${styles.input}`}
/>
<Button
className={styles.button}
style={{ background: genderInfo?.colorAssociation }}
onClick={handleNext}
disabled={isDisabled}
>
{t("next")}
</Button>
</Card>
</div>
</>
);
}
export default QuestionnairePage;

View File

@ -0,0 +1,51 @@
.img-wrapper {
display: block;
margin-inline: auto;
margin-top: 15px;
margin-inline: 25.8%;
border-radius: 50%;
aspect-ratio: 1 / 1;
}
.img-wrapper img {
width: 100%;
}
.question {
font-weight: 500;
margin-bottom: 14px;
}
.tip__text {
text-align: center;
color: #5050506b;
font-size: 17px;
font-weight: 400;
line-height: 20.57px;
margin-top: 30px;
}
.underlined {
text-decoration: underline;
}
.button {
margin-top: 18px;
}
.input > h3 {
display: none;
}
@media (max-width: 420px) {
:global(.page-mike) .button {
max-width: 290px;
}
.date__text {
font-size: 13px;
}
.tip__text {
margin-top: 20px;
font-size: 15px;
}
}

View File

@ -0,0 +1,240 @@
import styles from "./styles.module.css";
import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { actions, selectors } from "@/store";
import { useEffect, useState } from "react";
import { useDispatch, useSelector } from "react-redux";
import Title from "@/components/Title";
import { mikeTrialPaymentReviews } from "@/data/reviews";
import { trialPaymentPointsList } from "@/data/pointsLists";
import { Navigate, useNavigate, useParams } from "react-router-dom";
import { EPlacementKeys, IPaywallProduct } from "@/api/resources/Paywall";
import { usePaywall } from "@/hooks/paywall/usePaywall";
import routes from "@/routes";
import metricService, { EGoals } from "@/services/metric/metricService";
import Modal from "@/components/Modal";
import ButtonBack from "../../components/ButtonBack";
import { Message } from "../../components/Message";
import TrialPaymentHeader from "../../components/TrialPaymentHeader";
import { useStyle } from "@/routerComponents/Mike/v1/providers/StyleProvider/useStyle";
import PersonalInformation from "../../components/PersonalInformation";
import { useGenderInfo } from "../../lib/getGenderInfo";
import PaymentTable from "../../components/PaymentTable";
import Reviews from "../../components/Reviews";
import PointsList from "../../components/PointsList";
import OftenAsk from "../../components/OftenAsk";
import PaymentModal from "@/components/PaymentModal";
import { useTranslation } from "react-i18next";
function TrialPaymentPage() {
const dispatch = useDispatch();
const navigate = useNavigate();
const { t } = useTranslation();
const birthdate = useSelector(selectors.selectBirthdate);
const zodiacSign = getZodiacSignByDate(birthdate);
const { gender, birthPlace } = useSelector(selectors.selectQuestionnaire);
const { products } = usePaywall({
placementKey: EPlacementKeys["aura.placement.v1.mike"],
});
const activeProductFromStore = useSelector(selectors.selectActiveProduct);
const [activeProduct, setActiveProduct] = useState<IPaywallProduct | null>(
activeProductFromStore
);
const [isOpenPaymentModal, setIsOpenPaymentModal] = useState<boolean>(false);
const { setStyle } = useStyle();
const genderInfo = useGenderInfo();
const { subPlan } = useParams();
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);
dispatch(actions.payment.update({ activeProduct: targetProduct }));
}
}
}, [dispatch, subPlan, products]);
useEffect(() => {
if (!products.length) return;
const isActiveProduct = products.find(
(product) => product._id === activeProduct?._id
);
if (!activeProduct || !isActiveProduct) {
navigate(routes.client.trialChoiceV1());
}
}, [activeProduct, navigate, products]);
useEffect(() => {
setStyle({ backgroundColor: "#F0F0F0", paddingInline: "18px" });
}, [setStyle]);
if (!activeProduct) {
return <Navigate to={routes.client.trialChoiceV1()} />;
}
if (!birthdate || !gender || !birthPlace) {
return <Navigate to={routes.client.mikeV1() + routes.client.gender()} />;
}
const handleDiscount = () => {
setIsOpenPaymentModal(false);
// navigate(routes.client.additionalDiscount());
};
const openStripeModal = () => {
metricService.reachGoal(EGoals.AURA_PAYMENT_METHODS_OPENED);
setIsOpenPaymentModal(true);
};
const returnUrl = `${window.location.origin + routes.client.advisorChatPrivate("asst_WWkAlT4Ovs6gKRy6VEn9LqNS")}?paymentMadeChatMike=true`;
return (
<div className="page-mike">
<ButtonBack theme="light" />
<Modal
containerClassName={styles.modal}
open={isOpenPaymentModal}
onClose={handleDiscount}
type="hidden"
>
<PaymentModal
placementKey={EPlacementKeys["aura.placement.v1.mike"]}
returnUrl={returnUrl}
/>
</Modal>
<div className={styles["assistant-container"]}>
<img src="/mike/assistant_3.png" alt="assistant" />
<Message className={styles["assistant-message"]}>
{t("mike.trialPayment.message")}
</Message>
</div>
<TrialPaymentHeader
buttonClick={openStripeModal}
buttonText="START CHAT"
/>
<div className={styles["personal-information-container"]}>
<PersonalInformation
zodiacSign={zodiacSign}
gender={gender}
birthPlace={birthPlace}
birthdate={birthdate}
primaryColor={genderInfo?.subColorAssociation || genderInfo?.colorAssociation}
/>
</div>
{!!activeProduct && (
<>
<PaymentTable product={activeProduct} buttonClick={openStripeModal} />
</>
)}
<div className={styles["chat-private"] + " " + styles.full}>
<Title variant="h2" className={styles.title}>
Chat Private
</Title>
<div className={styles["chat-private-content"]}>
<div className={styles["chat-message"]}>
<p>Does my boyfried really love me?</p>
<div className={styles["chat-time"]}>
<span>8:16 AM</span>
<svg
width="10"
height="7"
viewBox="0 0 10 7"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M9.30623 0.892212L4.79973 6.12297L2.86837 4.16144M2.86837 6.12297L0.937012 4.16144M7.37487 0.892212L4.63878 4.07971"
stroke="white"
strokeWidth="0.630153"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</div>
</div>
<div className={styles["chat-sender"]}>
<div
className={
styles["chat-message"] + " " + styles["chat-sender-message"]
}
>
<p>
Of course! Based on yours and your boyfrieds natal charts your
are made for each other! The accuracy of an astrology reading
can vary and is subjective. Astrology is not an exact science,
but many find that it can provide valuable insights and
perspectives. Our platform uses advanced algorithms and expert
astrologers to provide the most accurate readings possible.
</p>
<p>
The accuracy of an astrology reading can vary and is subjective.
Astrology is not an exact science, but many find that it can
provide valuable insights and perspectives. Our platform uses
advanced algorithms and expert astrologers to provide the most
accurate r
</p>
</div>
<div className={styles["chat-main-avatar"]}>
<img src="/mike/chat1.png" alt="avatar" />
</div>
</div>
<div className={styles["chat-sending"]}>
<img
className={styles["chat-avatar"]}
src="/mike/chat2.webp"
alt="avatar"
/>
<span className={styles["chat-sending-circle"]}>
<span />
<span />
<span />
</span>
</div>
<div></div>
</div>
</div>
<div className={styles["reviews-container"]}>
<Title variant="h2" className={styles.title}>
Users love us
</Title>
<Reviews reviews={mikeTrialPaymentReviews} />
</div>
<div className={styles["points-list-container"]}>
<PointsList
points={trialPaymentPointsList}
title={
<Title variant="h2" className={styles.title}>
What you get
</Title>
}
/>
</div>
<div className={styles["often-ask-container"]}>
<OftenAsk
title={
<Title variant="h2" className={styles.title}>
People often ask
</Title>
}
/>
</div>
<div className={styles["payment-table-container"]}>
{!!activeProduct && (
<PaymentTable product={activeProduct} buttonClick={openStripeModal} />
)}
</div>
</div>
);
}
export default TrialPaymentPage;

View File

@ -0,0 +1,208 @@
.modal {
max-height: calc(100dvh - 40px);
}
.assistant-container {
position: relative;
width: calc(100% + 36px);
margin-top: -18px;
margin-inline: -18px;
}
.assistant-container img {
width: 100%;
height: 100%;
object-fit: contain;
object-position: top;
}
.assistant-message {
position: absolute;
bottom: 115px;
left: 15px;
right: 15px;
text-align: center;
}
.personal-information-container {
width: 100%;
margin-top: 30px;
}
.title {
margin-top: 50px;
margin-bottom: 18px;
font-size: 47px;
font-weight: 800;
}
.full {
width: 100%;
}
.chat-private .title {
margin-top: 30px;
margin-bottom: 12px;
}
.chat-private-content {
width: 100%;
}
.chat-message {
position: relative;
width: calc(80% - 30px);
margin-left: auto;
margin-right: 30px;
padding: 10px 16px 26px;
font-size: 15px;
line-height: 1.25;
background: #000;
color: #fff;
border-radius: 20px 20px 0px;
background-size: contain;
}
.chat-message::after {
content: '';
position: absolute;
bottom: -12px;
right: -4px;
width: 50px;
height: 68px;
background: #000;
clip-path: path('M 46 55 L 46 68 C 38 62 30 55 0.022 55 Z');
}
.chat-message p + p {
margin-top: 22px;
}
.chat-message .chat-time {
position: absolute;
display: flex;
align-items: center;
gap: 6px;
bottom: 10px;
right: 20px;
z-index: 1;
}
.chat-time span {
font-size: 5px;
}
.chat-sender {
display: flex;
gap: 8px;
margin-top: 20px;
}
.chat-sender-message {
flex-grow: 1;
margin-right: 0;
margin-left: 30px;
background: linear-gradient(90deg, #fab8c4 0%, #b48dd4 0.01%, #8973de 0.02%, #000 100%);
border-radius: 20px 20px 20px 0px;
}
.chat-sender-message::after {
right: auto;
left: 0;
width: 100%;
background: linear-gradient(90deg, #fab8c4 0%, #b48dd4 0.01%, #8973de 0.02%, #000 100%);
clip-path: path('M 0 55 L 0 68 C 12 56 30 55 55 55 Z');
}
.chat-sender-message::before {
content: '';
position: absolute;
width: 100%;
height: calc(100% - 42px);
bottom: 0;
left: 0;
border-radius: 20px;
backdrop-filter: blur(5px);
-webkit-backdrop-filter: blur(5px);
-webkit-mask: -webkit-gradient(
linear,
left 12%,
left 0%,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
mask: -webkit-gradient(
linear,
left 12%,
left 0%,
from(rgba(0, 0, 0, 1)),
to(rgba(0, 0, 0, 0))
);
}
.chat-sending {
margin-top: 20px;
display: flex;
align-items: center;
gap: 18px;
}
.chat-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
}
.chat-sending-circle {
display: flex;
gap: 4px;
border-radius: 20px;
padding: 12px;
background: #7B6BE1;
}
.chat-sending-circle span {
display: block;
width: 6px;
height: 6px;
border-radius: 6px;
background-color: #fff;
opacity: .9;
}
.chat-sending-circle span:nth-child(2) {
opacity: .8;
}
.chat-sending-circle span:nth-child(3) {
opacity: .7;
}
.reviews-container .title {
margin-top: 5px;
margin-bottom: 10px;
}
.points-list-container .title {
margin-top: 20px;
}
.often-ask-container .title {
margin-top: 50px;
margin-bottom: 15px;
}
.payment-table-container {
width: 100%;
margin-bottom: 40px;
}
.payment-table-container > div {
margin-top: 20px;
margin-bottom: 18px;
}
@media (max-width: 430px) {
.title {
font-size: 38px;
}
}

View File

@ -0,0 +1,16 @@
import styles from './styles.module.css'
import MainButton from "@/components/MainButton";
import { ButtonHTMLAttributes, FC } from "react";
import cn from "classnames";
type ButtonProps = ButtonHTMLAttributes<HTMLButtonElement> & {
color?: "black" | "blue";
};
export const Button: FC<ButtonProps> = ({ children, className, ...props }) => {
return (
<MainButton className={cn(className, styles.button)} {...props}>
{children}
</MainButton>
);
};

View File

@ -0,0 +1,10 @@
.button {
display: block;
margin-inline: auto;
max-width: 326px;
width: 100%;
min-height: 54px;
border-radius: 22px;
font-size: 25px;
font-weight: 600;
}

View File

@ -0,0 +1,14 @@
import styles from "./styles.module.css";
import { FC, PropsWithChildren } from "react";
import cn from "classnames";
interface CardProps {
className?: string;
}
export const Card: FC<PropsWithChildren<CardProps>> = ({
children,
className,
}) => {
return <div className={cn(styles.card, className)}>{children}</div>;
};

View File

@ -0,0 +1,23 @@
.card {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 22px;
padding: 14px 30px 30px 30px;
background-color: #fff;
border-radius: 16px;
}
.card :global(.description) {
font-weight: 500;
margin-inline: -10px;
}
@media (max-width: 420px) {
.card {
padding: 16px 20px;
}
.card :global(.description) {
line-height: 1.2;
font-size: 18px;
}
}

View File

@ -18,3 +18,19 @@ export const questions: IQuestion[] = [
text: "Yes, all readings on our platform are strictly confidential. We respect our users' privacy and ensure that all personal data and readings are securely stored and not shared with third parties without consent.",
},
];
export const questionsMike: IQuestion[] = [
{
title: "How can an astrological consultation help me in my life?",
text: "An astrological consultation can provide valuable insights into your personality traits, strengths, and weaknesses, and help you make important decisions and understand current life situations. Book your first consultation and discover new possibilities!",
},
{
title: "How does the astrological platform work and what does it offer?",
text: "The astrological platform offers online consultations with professional astrologers, personalized natal charts, predictions, and advice based on your birth time, date, and place.",
},
{
title:
"How accurate are the predictions and advice based on my natal chart?",
text: "The accuracy of predictions and advice depends on the accuracy of the provided data. With precise data, astrologers can offer very accurate and useful insights and recommendations.",
},
];

View File

@ -50,3 +50,27 @@ export const marketingLandingReviews: IReview[] = [
image: "/user3.webp",
},
];
export const mikeTrialPaymentReviews: IReview[] = [
{
username: "@andi36_11",
date: "06/05/2024",
text: '"Talking to Mike felt like chatting with an old friend. He mentioned some personality traits and life events that were spot-on, including a recent difficult breakup. His advice was very comforting and gave me hope."',
mark: 5,
image: "/mike/user1.webp",
},
{
username: "@aramaska",
date: "04/17/2024",
text: '"Mike was incredibly insightful and accurate. His guidance helped me decide to take a new job offer, which turned out to be the best career move I\'ve ever made."',
mark: 5,
image: "/mike/user2.webp",
},
{
username: "@patterso",
date: "03/01/2024",
text: '"Astrologer Mike provided clarity and direction when I needed it most. He predicted a significant change in my life, which happened when I moved to a new city. His insights helped me prepare and embrace the transition. Highly recommend his services!"',
mark: 5,
image: "/mike/user3.webp",
},
];

View File

@ -1,6 +1,73 @@
import { EPlacementKeys, IPaywall } from "@/api/resources/Paywall";
export const defaultPaywalls: { [key in EPlacementKeys]: IPaywall } = {
"aura.placement.v1.mike": {
"_id": "66a910b60ac8af1928d3d822",
"key": "aura.paywall.v1.mike.default",
"name": "Mike Paywall",
"products": [
{
"_id": "65ff043dfc0fcfc4be550035",
"key": "compatibility.pdf.trial.0",
"productId": "prod_PnStTEBzrPLgvL",
"name": "Сompatibility AURA | Trial $0.99",
"priceId": "price_1PG2RSIlX4lgwUxrDfU2BDS4",
"type": "subscription",
"description": "Description",
"discountPrice": null,
"discountPriceId": null,
"isDiscount": false,
"isFreeTrial": false,
"isTrial": true,
"price": 1900,
"trialDuration": 7,
"trialPrice": 99,
"trialPriceId": "price_1PFiSkIlX4lgwUxrVel0l445"
}
],
"properties": [
{
"key": "text.0",
"value": "We've helped millions of people to have happier lives and better relationships, and we want to help you too.",
"_id": "664542bbfe0a8eb4ee0b4f27"
},
{
"key": "text.0.color",
"value": "millions",
"_id": "664542bbfe0a8eb4ee0b4f28"
},
{
"key": "text.1",
"value": "Money shouldnt stand in the way of finding astrology guidance that finally works. So, choose an amount that you think is reasonable to try us out for one week.",
"_id": "664542bbfe0a8eb4ee0b4f29"
},
{
"key": "text.2",
"value": "It costs us $13.67 to offer a 3-day trial, but please choose the amount you are comfortable with.",
"_id": "664542bbfe0a8eb4ee0b4f2a"
},
{
"key": "text.3",
"value": "This option will help us support those who need to select the lowest trial prices!",
"_id": "664542bbfe0a8eb4ee0b4f2b"
},
{
"key": "text.4",
"value": "*Cost of trial as of February 2024",
"_id": "664542bbfe0a8eb4ee0b4f2c"
},
{
"key": "text.5",
"value": "${quantity} people joined today",
"_id": "664542bbfe0a8eb4ee0b4f2d"
},
{
"key": "text.button.1",
"value": "See my plan",
"_id": "664542bbfe0a8eb4ee0b4f2e"
}
]
},
"aura.placement.redesign.main": {
"_id": "664215e2859ff1199d3a6e9a",
"key": "aura.paywall.redesign.main",

View File

@ -102,6 +102,12 @@ export default {
"aura.name_1.review": "Samantha Green",
"aura.name_2.review": "James Wilson",
"aura.review_1.content": "As for me, money just doesn't stick with me at all. It turned out that it wasn't because I was bad or irresponsible, but it was because of my partner. We delved deeply into this issue thanks to Aura, and what do you think? Everything changed the very next day. Now we are happy and wealthy.",
"aura.review_2.content": "I don't know why, but I always had bad experiences in relationships and couldn't find the one who would understand and love me. So, I took a special extended test and immediately figured everything out. It turns out that the birth date of the chosen one and number coincidences are very important in relationships. Now I consider this in all areas, and it helps me a lot."
"aura.review_2.content": "I don't know why, but I always had bad experiences in relationships and couldn't find the one who would understand and love me. So, I took a special extended test and immediately figured everything out. It turns out that the birth date of the chosen one and number coincidences are very important in relationships. Now I consider this in all areas, and it helps me a lot.",
"mike.questionnaire.message": "Your date of birth will help me create your natal chart, lay out Tarot cards, understand your compatibility, and provide more accurate predictions.",
"mike.birthtime.message": "The time of birth determines the rising sign (ascendant) and the positions of the houses in the natal chart.",
"mike.birthtime.description": "What's your time of birth?",
"mike.birthplace.message": "Place of birth affects the calculation of the exact positions of the planets and houses at the moment of your birth.",
"mike.emailEnter.message": "Sign up to avoid losing access to your information",
"mike.trialPayment.message": "I am online and ready to help with your request.",
},
}

View File

@ -0,0 +1,35 @@
import { Outlet, useLocation } from "react-router-dom";
import styles from "./styles.module.css";
import { useEffect, useRef } from "react";
import { useSchemeColorByElement } from "@/hooks/useSchemeColorByElement";
import Footer from "@/components/Footer";
import classNames from "classnames";
import { useStyle } from "../providers/StyleProvider/useStyle";
function MainLayout() {
const mainRef = useRef<HTMLDivElement>(null);
const innerRef = useRef<HTMLDivElement>(null);
useSchemeColorByElement(mainRef.current, "section.page, .page, section", [
location,
]);
const { style } = useStyle();
const { pathname } = useLocation();
useEffect(() => {
innerRef.current?.scrollTo({
top: 0,
left: 0,
});
}, [pathname]);
return (
<main className={classNames(styles.main)} ref={mainRef}>
<section className={styles.content} style={style} ref={innerRef}>
<Outlet />
<Footer />
</section>
</main>
);
}
export default MainLayout;

View File

@ -0,0 +1,31 @@
:global(.page-mike) {
display: flex;
flex-direction: column;
flex: 1 1;
width: inherit;
position: static;
padding: 0;
align-items: center;
overflow: initial;
}
:global(.page-footer) {
font-size: 18px;
font-weight: 500;
color: #797979;
}
.content {
display: flex;
position: relative;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
padding: 14px 14px 0px;
overflow-y: auto;
}
.main {
margin-inline: auto;
height: 100svh;
max-width: 430px;
}

View File

@ -0,0 +1,47 @@
import { Route, Routes } from "react-router-dom";
import MainLayout from "./Layouts/MainLayout";
import routes from "@/routes";
import GenderPage from "@/components/pages/Mike/v1/pages/Gender";
import QuestionnairePage from "@/components/pages/Mike/v1/pages/Questionnaire";
import BirthTimePage from "@/components/pages/Mike/v1/pages/BirthTimePage";
import { StyleProvider } from "./providers/StyleProvider/StyleProvider";
import BirthPlacePage from "@/components/pages/Mike/v1/pages/BirthPlace";
import EmailEnterPage from "@/components/pages/Mike/v1/pages/EmailEnterPage";
import TrialPaymentPage from "@/components/pages/Mike/v1/pages/TrialPayment";
function MikeV1Routes() {
return (
<StyleProvider>
<Routes>
<Route element={<MainLayout />}>
<Route path={routes.client.gender()} element={<GenderPage />}>
<Route path=":targetId/*" element={<GenderPage />} />
</Route>
<Route
path={routes.client.questionnaire()}
element={<QuestionnairePage />}
>
<Route path=":stepId" element={<QuestionnairePage />}>
<Route path=":question" element={<QuestionnairePage />} />
</Route>
</Route>
<Route path={routes.client.birthtime()} element={<BirthTimePage />} />
<Route
path={routes.client.getBirthPlace()}
element={<BirthPlacePage />}
/>
<Route
path={routes.client.emailEnter()}
element={<EmailEnterPage />}
/>
<Route
path={routes.client.trialPayment()}
element={<TrialPaymentPage />}
/>
</Route>
</Routes>
</StyleProvider>
);
}
export default MikeV1Routes;

View File

@ -0,0 +1,29 @@
import {
CSSProperties,
FC,
PropsWithChildren,
createContext,
useState,
} from "react";
interface IStyleContext {
style: CSSProperties | undefined;
setStyle: (css: CSSProperties | undefined) => void;
}
export const StyleContext = createContext<IStyleContext | undefined>(undefined);
interface IStyleProviderProps {
defaultCss?: CSSProperties;
}
export const StyleProvider: FC<PropsWithChildren<IStyleProviderProps>> = ({
children,
defaultCss = {},
}) => {
const [style, setStyle] = useState<CSSProperties | undefined>(defaultCss);
return (
<StyleContext.Provider value={{ style, setStyle }}>
{children}
</StyleContext.Provider>
);
};

View File

@ -0,0 +1,10 @@
import { useContext } from "react";
import { StyleContext } from "./StyleProvider";
export const useStyle = () => {
const context = useContext(StyleContext);
if (context === undefined) {
throw new Error("useStyleContext must be used within a StyleProvider");
}
return context;
};

View File

@ -150,7 +150,9 @@ const routes = {
[host, "single-payment", productId].join("/"),
getInformationPartner: () => [host, "get-information-partner"].join("/"),
mikeV1: () => [host, "v1", "mike"].join("/"),
getBirthPlace: () => [host, "birthPlace"].join("/"),
// ABDesignV1
genderV1: () => [host, "v1", "gender"].join("/"),
questionnaireV1: () => [host, "v1", "questionnaire"].join("/"),
@ -489,6 +491,7 @@ export const withoutHeaderRoutes = [
routes.client.epeSuccessPayment(),
routes.client.getInformationPartner(),
routes.client.advisorChatPrivate(),
routes.client.gender()
];
export const hasNoHeader = (path: string) => {
let result = true;

View File

@ -51,6 +51,7 @@ import userConfig, {
selectIsShowTryApp,
selectFeature,
selectIsForceShortPath,
selectDateOfPaymentChatMike,
} from "./userConfig";
import compatibilities, {
actions as compatibilitiesActions,
@ -125,6 +126,7 @@ export const selectors = {
selectIsShowTryApp,
selectFeature,
selectIsForceShortPath,
selectDateOfPaymentChatMike,
selectOpenAiToken,
selectTrialChoiceArrowOptions,
selectPalmistryLines,

View File

@ -17,12 +17,14 @@ type IPayloadUpdatePaywall = {
}
const initialState: TPaywalls = {
"aura.placement.v1.mike": null,
"aura.placement.main": null,
"aura.placement.redesign.main": null,
"aura.placement.email.marketing": null,
"aura.placement.secret.discount": null,
"aura.placement.palmistry.main": null,
isMustUpdate: {
"aura.placement.v1.mike": true,
"aura.placement.main": true,
"aura.placement.redesign.main": true,
"aura.placement.email.marketing": true,

View File

@ -11,6 +11,7 @@ interface IUserConfig {
isShowTryApp: boolean;
isForceShortPath: boolean;
feature: string;
dateOfPaymentChatMike: string;
}
const initialState: IUserConfig = {
@ -18,6 +19,7 @@ const initialState: IUserConfig = {
isShowTryApp: false,
isForceShortPath: false,
feature: "",
dateOfPaymentChatMike: "",
};
const userConfigSlice = createSlice({
@ -43,6 +45,10 @@ const userConfigSlice = createSlice({
state.feature = action.payload;
return state;
},
setDateOfPaymentChatMike(state, action: PayloadAction<Date>) {
state.dateOfPaymentChatMike = action.payload.toISOString();
return state;
},
},
extraReducers: (builder) => builder.addCase("reset", () => initialState),
});
@ -64,4 +70,8 @@ export const selectFeature = createSelector(
(state: { userConfig: IUserConfig }) => state.userConfig.feature,
(userConfig) => userConfig
);
export const selectDateOfPaymentChatMike = createSelector(
(state: { userConfig: IUserConfig }) => state.userConfig.dateOfPaymentChatMike,
(userConfig) => userConfig
);
export default userConfigSlice.reducer;