Develop
BIN
public/bg-gradient.png
Normal file
|
After Width: | Height: | Size: 247 KiB |
BIN
public/bg-location-map.webp
Normal file
|
After Width: | Height: | Size: 595 KiB |
BIN
public/mike/assistant_1.webp
Normal file
|
After Width: | Height: | Size: 83 KiB |
BIN
public/mike/assistant_2.webp
Normal file
|
After Width: | Height: | Size: 139 KiB |
BIN
public/mike/assistant_3.png
Normal file
|
After Width: | Height: | Size: 278 KiB |
BIN
public/mike/avatar.webp
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/mike/avatar_2.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
public/mike/avatar_3.webp
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
public/mike/bg-gradient-female.webp
Normal file
|
After Width: | Height: | Size: 213 KiB |
BIN
public/mike/bg-gradient-male.webp
Normal file
|
After Width: | Height: | Size: 216 KiB |
BIN
public/mike/chat-private.png
Normal file
|
After Width: | Height: | Size: 184 KiB |
BIN
public/mike/chat1.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/mike/chat2.webp
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
BIN
public/mike/female.webp
Normal file
|
After Width: | Height: | Size: 38 KiB |
BIN
public/mike/male.webp
Normal file
|
After Width: | Height: | Size: 40 KiB |
BIN
public/mike/user1.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/mike/user2.webp
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/mike/user3.webp
Normal file
|
After Width: | Height: | Size: 19 KiB |
@ -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",
|
||||
|
||||
@ -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} />
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}
|
||||
|
||||
@ -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"
|
||||
|
||||
36
src/components/pages/Mike/v1/components/ButtonBack/index.tsx
Normal 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;
|
||||
@ -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
|
||||
}
|
||||
21
src/components/pages/Mike/v1/components/Link/index.tsx
Normal 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;
|
||||
@ -0,0 +1,7 @@
|
||||
.link {
|
||||
margin-block: 0 10px;
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
text-decoration: underline;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
19
src/components/pages/Mike/v1/components/Message/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
@ -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;
|
||||
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
31
src/components/pages/Mike/v1/components/OftenAsk/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
46
src/components/pages/Mike/v1/components/PointsList/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
45
src/components/pages/Mike/v1/components/Reviews/Review.tsx
Normal 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;
|
||||
23
src/components/pages/Mike/v1/components/Reviews/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
@ -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;
|
||||
@ -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;
|
||||
}
|
||||
26
src/components/pages/Mike/v1/data/data.ts
Normal 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",
|
||||
},
|
||||
];
|
||||
10
src/components/pages/Mike/v1/lib/getGenderInfo.ts
Normal 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;
|
||||
}
|
||||
99
src/components/pages/Mike/v1/pages/BirthPlace/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
68
src/components/pages/Mike/v1/pages/BirthTimePage/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
231
src/components/pages/Mike/v1/pages/EmailEnterPage/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
102
src/components/pages/Mike/v1/pages/Gender/index.tsx
Normal 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;
|
||||
99
src/components/pages/Mike/v1/pages/Gender/styles.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
84
src/components/pages/Mike/v1/pages/Questionnaire/index.tsx
Normal 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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
240
src/components/pages/Mike/v1/pages/TrialPayment/index.tsx
Normal 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 your’s and your boyfried’s 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;
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
16
src/components/pages/Mike/v1/ui/Button/index.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
10
src/components/pages/Mike/v1/ui/Button/styles.module.css
Normal 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;
|
||||
}
|
||||
14
src/components/pages/Mike/v1/ui/Card/index.tsx
Normal 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>;
|
||||
};
|
||||
23
src/components/pages/Mike/v1/ui/Card/styles.module.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@ -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.",
|
||||
},
|
||||
];
|
||||
|
||||
@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@ -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 shouldn’t 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",
|
||||
|
||||
@ -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.",
|
||||
},
|
||||
}
|
||||
|
||||
35
src/routerComponents/Mike/v1/Layouts/MainLayout.tsx
Normal 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;
|
||||
31
src/routerComponents/Mike/v1/Layouts/styles.module.css
Normal 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;
|
||||
}
|
||||
47
src/routerComponents/Mike/v1/index.tsx
Normal 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;
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
@ -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;
|
||||
};
|
||||
@ -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;
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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;
|
||||
|
||||