This commit is contained in:
Daniil Chemerkin 2025-02-27 19:46:50 +00:00
parent d0c61ca094
commit 2384081811
8 changed files with 136 additions and 87 deletions

View File

@ -2,7 +2,7 @@ import routes, { compatibilityV2Prefix } from "@/routes";
import styles from "./styles.module.scss"; import styles from "./styles.module.scss";
import Title from "@/components/Title"; import Title from "@/components/Title";
import Button from "../../components/Button"; import Button from "../../components/Button";
import { useLocation, useNavigate } from "react-router-dom"; import { Navigate, useLocation, useNavigate } from "react-router-dom";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
import { useDispatch } from "react-redux"; import { useDispatch } from "react-redux";
@ -33,6 +33,8 @@ function FindHappiness() {
} }
}, [dispatch, location.pathname]); }, [dispatch, location.pathname]);
return <Navigate to={routes.client.compatibilityV2Gender()} />
if (!ready) return null; if (!ready) return null;
return ( return (

View File

@ -9,7 +9,7 @@
} }
.begin-trial { .begin-trial {
margin-top: -20px; margin-top: 20px;
} }
.why-love { .why-love {
@ -23,10 +23,10 @@
} }
.success-story { .success-story {
font-size: 20px; // font-size: 20px;
font-weight: 500; font-weight: 500;
margin-top: 22px; margin-top: 22px;
line-height: 1 !important; line-height: 120% !important;
} }
.as-seen-in { .as-seen-in {

View File

@ -1,7 +1,6 @@
import Title from "@/components/Title" import Title from "@/components/Title"
import styles from "./styles.module.scss" import styles from "./styles.module.scss"
import { IPaywallProduct } from "@/api/resources/Paywall" import { IPaywallProduct } from "@/api/resources/Paywall"
import { useState, useEffect } from "react"
import { useTranslations } from "@/hooks/translations" import { useTranslations } from "@/hooks/translations"
import { ELocalesPlacement } from "@/locales" import { ELocalesPlacement } from "@/locales"
import { useEmailsGeneration } from "@/hooks/emailsGeneration/useEmailsGeneration" import { useEmailsGeneration } from "@/hooks/emailsGeneration/useEmailsGeneration"
@ -12,34 +11,12 @@ interface IEmailsListProps {
function EmailsList({ products }: IEmailsListProps) { function EmailsList({ products }: IEmailsListProps) {
const { translate } = useTranslations(ELocalesPlacement.PalmistryV1); const { translate } = useTranslations(ELocalesPlacement.PalmistryV1);
const [countBuyingEmails, setCountBuyingEmails] = useState(3);
const { const {
displayEmails, displayEmails,
countBoughtEmails countBoughtEmails
} = useEmailsGeneration(products); } = useEmailsGeneration(products);
useEffect(() => {
const updateCount = () => {
const change = Math.floor(Math.random() * 3) + 1; // 1, 2 или 3
const increase = Math.random() < 0.5;
setCountBuyingEmails(prev => {
const newCount = increase ? prev + change : prev - change;
if (newCount < 1) return 1;
if (newCount > 5) return 5;
return newCount;
});
setTimeout(updateCount, 1000 + Math.random() * 3000);
};
updateCount();
return () => {
setCountBuyingEmails(17);
};
}, []);
return ( return (
<div className={styles.container}> <div className={styles.container}>
<div className={styles.header}> <div className={styles.header}>
@ -51,23 +28,22 @@ function EmailsList({ products }: IEmailsListProps) {
</div> </div>
<p className={styles.description}> <p className={styles.description}>
{translate("/trial-choice.v1.emails_list.description", { {translate("/trial-choice.v1.emails_list.description", {
count: countBuyingEmails count: displayEmails?.length
})} })}
</p> </p>
<div className={styles.emails}> <div className={styles.emails}>
{displayEmails.map((item) => ( {displayEmails.map((item) => (
<div key={item.id} className={styles.emailContainer}> <div key={item.id} className={`${styles.emailContainer} ${item.willBeRemoved ? styles.removed : ""}`}>
<div className={styles.priceContainer}> <div className={styles.priceContainer}>
{item.willBeRemoved && ( {item.willBeRemoved && (
<svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg"> <svg width="17" height="17" viewBox="0 0 17 17" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="17" height="17" rx="8.5" fill="url(#paint0_linear_263_585)" /> <rect
width="17"
height="17"
rx="8.5"
fill="#62DFA1"
/>
<path d="M7.2586 12C7.08625 12 6.9139 11.9285 6.78205 11.785L4.19724 8.96395C4.13481 8.89628 4.0852 8.8154 4.05132 8.7261C4.01745 8.63679 4 8.54086 4 8.44395C4 8.34704 4.01745 8.2511 4.05132 8.1618C4.0852 8.07249 4.13481 7.99162 4.19724 7.92394C4.32473 7.78583 4.49574 7.70853 4.67379 7.70853C4.85184 7.70853 5.02285 7.78583 5.15034 7.92394L7.2586 10.2245L11.8491 5.21541C11.9766 5.0773 12.1476 5 12.3256 5C12.5037 5 12.6747 5.0773 12.8022 5.21541C12.8648 5.28298 12.9145 5.36382 12.9485 5.45314C12.9825 5.54246 13 5.63844 13 5.73541C13 5.83239 12.9825 5.92837 12.9485 6.01769C12.9145 6.10701 12.8648 6.18785 12.8022 6.25542L7.73515 11.7845C7.60814 11.9234 7.43683 12.0009 7.2586 12Z" fill="#F4F4F4" /> <path d="M7.2586 12C7.08625 12 6.9139 11.9285 6.78205 11.785L4.19724 8.96395C4.13481 8.89628 4.0852 8.8154 4.05132 8.7261C4.01745 8.63679 4 8.54086 4 8.44395C4 8.34704 4.01745 8.2511 4.05132 8.1618C4.0852 8.07249 4.13481 7.99162 4.19724 7.92394C4.32473 7.78583 4.49574 7.70853 4.67379 7.70853C4.85184 7.70853 5.02285 7.78583 5.15034 7.92394L7.2586 10.2245L11.8491 5.21541C11.9766 5.0773 12.1476 5 12.3256 5C12.5037 5 12.6747 5.0773 12.8022 5.21541C12.8648 5.28298 12.9145 5.36382 12.9485 5.45314C12.9825 5.54246 13 5.63844 13 5.73541C13 5.83239 12.9825 5.92837 12.9485 6.01769C12.9145 6.10701 12.8648 6.18785 12.8022 6.25542L7.73515 11.7845C7.60814 11.9234 7.43683 12.0009 7.2586 12Z" fill="#F4F4F4" />
<defs>
<linearGradient id="paint0_linear_263_585" x1="8.5" y1="17" x2="8.5" y2="0" gradientUnits="userSpaceOnUse">
<stop stopColor="#00D26A" />
<stop offset="1" stopColor="#62DFA1" />
</linearGradient>
</defs>
</svg> </svg>
)} )}
<div className={styles.price}> <div className={styles.price}>

View File

@ -52,6 +52,13 @@
padding-inline: 16px; padding-inline: 16px;
gap: 4px; gap: 4px;
width: 100%; width: 100%;
opacity: 1;
transition: opacity 1.5s ease-in-out;
will-change: opacity;
&.removed {
opacity: 0;
}
&>.priceContainer { &>.priceContainer {
display: flex; display: flex;

View File

@ -4,7 +4,7 @@ import { addCurrency, ELocalesPlacement } from "@/locales";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { usePaywall } from "@/hooks/paywall/usePaywall"; import { usePaywall } from "@/hooks/paywall/usePaywall";
import { useState } from "react"; import { useEffect, useState } from "react";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import routes from "@/routes"; import routes from "@/routes";
@ -30,6 +30,7 @@ function TrialChoiceV1() {
placementKey: EPlacementKeys["aura.placement.palmistry.redesign"], placementKey: EPlacementKeys["aura.placement.palmistry.redesign"],
localesPlacement: ELocalesPlacement.PalmistryV1, localesPlacement: ELocalesPlacement.PalmistryV1,
}); });
const popularProduct = products[products.length - 1];
// const { flags } = useMetricABFlags(); // const { flags } = useMetricABFlags();
// const isLongText = flags?.text?.[0] === "on"; // const isLongText = flags?.text?.[0] === "on";
@ -63,6 +64,15 @@ function TrialChoiceV1() {
// metricService.reachGoal(EGoals.AURA_TRIAL_CHOICE_PAGE_VISIT, [EMetrics.KLAVIYO]); // metricService.reachGoal(EGoals.AURA_TRIAL_CHOICE_PAGE_VISIT, [EMetrics.KLAVIYO]);
// }, []); // }, []);
useEffect(() => {
if (popularProduct) {
dispatch(actions.payment.update({
activeProduct: popularProduct
}))
setIsDisabled(false);
}
}, [popularProduct])
return ( return (
<div className={styles.container}> <div className={styles.container}>
{!isLoading && ( {!isLoading && (
@ -106,6 +116,7 @@ function TrialChoiceV1() {
classNamePricesContainer={styles["prices-container"]} classNamePricesContainer={styles["prices-container"]}
currency={currency} currency={currency}
click={handlePriceItem} click={handlePriceItem}
preActiveItems={[popularProduct?._id]}
/> />
</div> </div>

View File

@ -114,8 +114,8 @@
.button { .button {
margin-top: 20px; margin-top: 20px;
transition: background 0.2s ease, color 0.2s ease; transition: background 0.2s ease, color 0.2s ease;
position: sticky; // position: sticky;
bottom: calc(0dvh + 16px); // bottom: calc(0dvh + 16px);
&:disabled { &:disabled {
border: solid #224e90 1px; border: solid #224e90 1px;
@ -150,7 +150,7 @@
font-weight: 500; font-weight: 500;
line-height: 125%; line-height: 125%;
color: #2C2C2C; color: #2C2C2C;
// list-style-type: disc; list-style-type: disc;
margin-left: 24px; margin-left: 24px;
&::marker { &::marker {

View File

@ -26,7 +26,7 @@
font-size: 20px; font-size: 20px;
font-weight: 500; font-weight: 500;
margin-top: 22px; margin-top: 22px;
line-height: 1 !important; line-height: 120% !important;
} }
.as-seen-in { .as-seen-in {

View File

@ -11,12 +11,21 @@ interface DisplayEmail {
willBeRemoved: boolean; willBeRemoved: boolean;
} }
interface EmailsToDelete {
emails: DisplayEmail[];
deleteAt: number;
markAt: number;
marked: boolean;
}
export const useEmailsGeneration = (products: Array<IPaywallProduct & { weight: number }>) => { export const useEmailsGeneration = (products: Array<IPaywallProduct & { weight: number }>) => {
const { translate } = useTranslations(ELocalesPlacement.EmailGenerator); const { translate } = useTranslations(ELocalesPlacement.EmailGenerator);
const [displayEmails, setDisplayEmails] = useState<DisplayEmail[]>([]); const [displayEmails, setDisplayEmails] = useState<DisplayEmail[]>([]);
const [countBoughtEmails, setCountBoughtEmails] = useState(758); const [countBoughtEmails, setCountBoughtEmails] = useState(758);
const maxEmails = 4; const [emailsToDeleteQueue, setEmailsToDeleteQueue] = useState<EmailsToDelete[]>([]);
const minEmails = 3;
const minEmails = 1;
const maxEmails = 5;
const getRandomProduct = () => { const getRandomProduct = () => {
const totalWeight = products.reduce((sum, product) => sum + (product.weight || 1), 0); const totalWeight = products.reduce((sum, product) => sum + (product.weight || 1), 0);
@ -27,7 +36,7 @@ export const useEmailsGeneration = (products: Array<IPaywallProduct & { weight:
if (random <= 0) return product; if (random <= 0) return product;
} }
return products[0]; return products[0];
}; }
const createEmail = () => { const createEmail = () => {
const product = getRandomProduct(); const product = getRandomProduct();
@ -41,53 +50,97 @@ export const useEmailsGeneration = (products: Array<IPaywallProduct & { weight:
id: Date.now() + Math.random(), id: Date.now() + Math.random(),
willBeRemoved: false willBeRemoved: false
}; };
}; }
// creating emails
useEffect(() => { useEffect(() => {
const addEmails = () => { if (!products?.length) return;
const count = Math.random() < 0.7 ? 1 : 2; addEmails(3);
const newEmails = Array(count).fill(null).map(createEmail);
setDisplayEmails(prev => {
const updatedEmails = [...prev, ...newEmails].slice(-maxEmails);
if (updatedEmails.length < minEmails) {
const additionalCount = minEmails - updatedEmails.length;
const additionalEmails = Array(additionalCount).fill(null).map(createEmail);
return [...updatedEmails, ...additionalEmails];
}
return updatedEmails;
});
const deleteTime = 3000 + Math.random() * 6000;
setTimeout(() => {
setDisplayEmails(prev =>
prev.map(email =>
newEmails.includes(email)
? { ...email, willBeRemoved: true }
: email
)
);
}, deleteTime - 1000);
setTimeout(() => {
setDisplayEmails(prev => {
const filteredEmails = prev.filter(email => !newEmails.includes(email));
setCountBoughtEmails(prev => prev + +(filteredEmails.length >= minEmails));
return filteredEmails.length >= minEmails ? (filteredEmails) : prev;
});
}, deleteTime);
setTimeout(addEmails, 1000 + Math.random() * 3000);
};
setDisplayEmails(Array(minEmails).fill(null).map(createEmail));
addEmails();
return () => setDisplayEmails([]); return () => setDisplayEmails([]);
}, [products]); }, [products]);
const addEmails = (countEmails?: number) => {
const addEmailsTimeout = 3000 + Math.random() * 2000;
const count = !!countEmails ? countEmails : Math.random() < 0.6 ? 1 : 2;
const _newEmails = Array(count).fill(null).map(createEmail);
setDisplayEmails(prev => {
const updatedEmails = [...prev, ..._newEmails].slice(-maxEmails);
if (updatedEmails.length < minEmails) {
const additionalCount = minEmails - updatedEmails.length;
const additionalEmails = Array(additionalCount).fill(null).map(createEmail);
return [...updatedEmails, ...additionalEmails];
}
return updatedEmails;
});
const now = Date.now();
setEmailsToDeleteQueue(prev => [...prev, ..._newEmails.map((value) => {
const deleteTime = 3000 + Math.random() * 10000;
return {
emails: [value],
deleteAt: now + deleteTime,
markAt: now + deleteTime - 1500,
marked: false
}
})]);
const timeoutAddEmails = setTimeout(() => {
addEmails();
clearTimeout(timeoutAddEmails);
}, addEmailsTimeout);
}
useEffect(() => {
if (emailsToDeleteQueue.length === 0) return;
const checkQueue = () => {
const now = Date.now();
setEmailsToDeleteQueue(prev => {
const updatedQueue = [...prev];
let hasChanges = false;
updatedQueue.forEach(item => {
if (displayEmails.length <= minEmails) return;
if (!item.marked && now >= item.markAt) {
markEmails(item.emails);
item.marked = true;
hasChanges = true;
}
if (now >= item.deleteAt) {
deleteEmails(item.emails);
hasChanges = true;
}
});
return hasChanges ? updatedQueue.filter(item => now < item.deleteAt) : updatedQueue;
});
};
const interval = setInterval(checkQueue, 100);
return () => clearInterval(interval);
}, [emailsToDeleteQueue]);
const deleteEmails = (emailsToDelete: DisplayEmail[]) => {
setDisplayEmails(prev =>
prev.filter(email => !emailsToDelete.some(e => e.id === email.id))
);
setCountBoughtEmails(prev => prev + emailsToDelete.length);
}
const markEmails = (emailsToMark: DisplayEmail[]) => {
setDisplayEmails(prev =>
prev.map(email =>
emailsToMark.some(e => e.id === email.id)
? { ...email, willBeRemoved: true }
: email
)
);
}
return { displayEmails, countBoughtEmails }; return { displayEmails, countBoughtEmails };
}; };