Merge branch 'develop' into 'main'

hint-palm

See merge request witapp/aura-webapp!673
This commit is contained in:
Daniil Chemerkin 2025-03-07 16:32:01 +00:00
commit 51bfc8a029
12 changed files with 439 additions and 157 deletions

View File

@ -1,9 +1,7 @@
import styles from "./styles.module.scss"; import styles from "./styles.module.scss";
import Title from "@/components/Title";
import { useDispatch, useSelector } from "react-redux"; import { useDispatch, useSelector } from "react-redux";
import { actions, selectors } from "@/store"; import { actions, selectors } from "@/store";
import { Gender } from "@/data"; import { Gender } from "@/data";
import PrivacyPolicy from "@/components/pages/ABDesign/v1/components/PrivacyPolicy";
// import Toast from "@/components/pages/ABDesign/v1/components/Toast"; // import Toast from "@/components/pages/ABDesign/v1/components/Toast";
import { useTranslations } from "@/hooks/translations"; import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales"; import { ELocalesPlacement } from "@/locales";
@ -17,10 +15,11 @@ import { usePreloadImages } from "@/hooks/preload/images";
import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie"; import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie";
import { useSession } from "@/hooks/session/useSession"; import { useSession } from "@/hooks/session/useSession";
import { EGender, ESourceAuthorization } from "@/api/resources/User"; import { EGender, ESourceAuthorization } from "@/api/resources/User";
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
import Answer from "../../components/Answer";
import Loader, { LoaderColor } from "@/components/Loader"; import Loader, { LoaderColor } from "@/components/Loader";
import { useUnleash } from "@/hooks/ab/unleash/useUnleash"; import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import GenderV0 from "./variants/GenderV0";
import GenderV1 from "./variants/GenderV1";
import GenderV2 from "./variants/GenderV2";
function GenderPage() { function GenderPage() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -41,7 +40,7 @@ function GenderPage() {
const { flags, ready } = useMetricABFlags(); const { flags, ready } = useMetricABFlags();
const { isReady, variant: genderPageType } = useUnleash({ const { isReady, variant: genderPageType } = useUnleash({
flag: "genderPageType" flag: EUnleashFlags.genderPageType
}); });
const pageType = flags?.genderPageType?.[0] || genderPageType || "v2"; const pageType = flags?.genderPageType?.[0] || genderPageType || "v2";
@ -115,142 +114,19 @@ function GenderPage() {
switch (pageType) { switch (pageType) {
case "v0": case "v0":
return ( return (
<> <GenderV0 localGenders={localGenders} gender={gender} selectGender={selectGender} />
<Title variant="h2" className={styles.title}>
{translate("/gender.title")}
</Title>
<p className={styles.description}>{translate("/gender.description", {
br: <br />,
})}</p>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
) )
case "v1": case "v1":
return ( return (
<> <GenderV1 localGenders={localGenders} gender={gender} selectGender={selectGender} />
<Title variant="h2" className={styles.title}>
{translate("/gender.v1.title", {
br: <br />,
})}
</Title>
<p className={styles.subtitle}>{translate("/gender.v1.subtitle", {
br: <br />,
})}</p>
<ul className={styles.points}>
{Array.from({ length: 4 }).map((_, index) => (
<li key={index}>
{translate(`/gender.v1.points.point${index + 1}`)}
</li>
))}
</ul>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
) )
case "v2": case "v2":
return ( return (
<> <GenderV2 localGenders={localGenders} gender={gender} selectGender={selectGender} />
<Title variant="h2" className={styles.title}>
{translate("/gender.v2.title", {
br: <br />,
})}
</Title>
<ul className={styles.points}>
{Array.from({ length: 5 }).map((_, index) => (
<li key={index}>
{translate(`/gender.v2.points.point${index + 1}`)}
</li>
))}
</ul>
<p
className={styles.subtitle}
style={{
marginTop: "28px",
}}
>{translate("/gender.v2.subtitle", {
br: <br />,
})}</p>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
) )
default: default:
return ( return (
<> <GenderV0 localGenders={localGenders} gender={gender} selectGender={selectGender} />
<Title variant="h2" className={styles.title}>
{translate("/gender.title")}
</Title>
<p className={styles.description}>{translate("/gender.description", {
br: <br />,
})}</p>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
); );
} }

View File

@ -0,0 +1,53 @@
import Title from "@/components/Title"
import styles from "../../styles.module.scss"
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import PrivacyPolicy from "@/components/pages/ABDesign/v1/components/PrivacyPolicy";
import { Gender } from "@/data";
import { genders } from "@/components/pages/ABDesign/v1/data/genders";
import Answer from "@/components/CompatibilityV2/components/Answer";
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
interface IGenderV0Props {
localGenders: Array<{
id: string;
title: React.ReactNode | string;
}>;
gender: string;
selectGender: (gender: Gender | null) => void;
}
function GenderV0({ localGenders, gender, selectGender }: IGenderV0Props) {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
return (
<>
<Title variant="h2" className={styles.title}>
{translate("/gender.title")}
</Title>
<p className={styles.description}>{translate("/gender.description", {
br: <br />,
})}</p>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
)
}
export default GenderV0

View File

@ -0,0 +1,62 @@
import Title from "@/components/Title"
import styles from "../../styles.module.scss"
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import PrivacyPolicy from "@/components/pages/ABDesign/v1/components/PrivacyPolicy";
import { Gender } from "@/data";
import { genders } from "@/components/pages/ABDesign/v1/data/genders";
import Answer from "@/components/CompatibilityV2/components/Answer";
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
interface IGenderV1Props {
localGenders: Array<{
id: string;
title: React.ReactNode | string;
}>;
gender: string;
selectGender: (gender: Gender | null) => void;
}
function GenderV1({ localGenders, gender, selectGender }: IGenderV1Props) {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
return (
<>
<Title variant="h2" className={styles.title}>
{translate("/gender.v1.title", {
br: <br />,
})}
</Title>
<p className={styles.subtitle}>{translate("/gender.v1.subtitle", {
br: <br />,
})}</p>
<ul className={styles.points}>
{Array.from({ length: 4 }).map((_, index) => (
<li key={index}>
{translate(`/gender.v1.points.point${index + 1}`)}
</li>
))}
</ul>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
)
}
export default GenderV1

View File

@ -0,0 +1,67 @@
import Title from "@/components/Title"
import styles from "../../styles.module.scss"
import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import PrivacyPolicy from "@/components/pages/ABDesign/v1/components/PrivacyPolicy";
import { Gender } from "@/data";
import { genders } from "@/components/pages/ABDesign/v1/data/genders";
import Answer from "@/components/CompatibilityV2/components/Answer";
import AlreadyHaveAccount from "@/components/ui/AlreadyHaveAccount";
interface IGenderV2Props {
localGenders: Array<{
id: string;
title: React.ReactNode | string;
}>;
gender: string;
selectGender: (gender: Gender | null) => void;
}
function GenderV2({ localGenders, gender, selectGender }: IGenderV2Props) {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
return (
<>
<Title variant="h2" className={styles.title}>
{translate("/gender.v2.title", {
br: <br />,
})}
</Title>
<ul className={styles.points}>
{Array.from({ length: 5 }).map((_, index) => (
<li key={index}>
{translate(`/gender.v2.points.point${index + 1}`)}
</li>
))}
</ul>
<p
className={styles.subtitle}
style={{
marginTop: "28px",
}}
>{translate("/gender.v2.subtitle", {
br: <br />,
})}</p>
{/* <ChooseGender onSelectGender={selectGender} /> */}
<div className={styles["genders-container"]}>
{localGenders.map((_gender, index) => (
<Answer
key={index}
answer={_gender}
isSelected={gender === _gender.id}
onClick={() => selectGender(genders.find((g) => g.id === _gender.id) ?? null)}
/>
))}
</div>
<PrivacyPolicy containerClassName={styles["privacy-policy"]} haveCheckbox={false} />
<AlreadyHaveAccount text={translate("/gender.already_have_account")} />
{/* {gender && !privacyPolicyChecked && (
<Toast classNameContainer={styles["toast-container"]} variant="error">
{translate("/gender.toast", undefined, ELocalesPlacement.V1)}
</Toast>
)} */}
</>
)
}
export default GenderV2

View File

@ -12,6 +12,8 @@ import { selectors } from "@/store";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { images } from "../../data"; import { images } from "../../data";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import Loader, { LoaderColor } from "@/components/Loader";
function PalmsInformation() { function PalmsInformation() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -24,10 +26,18 @@ function PalmsInformation() {
preloadKey: ELottieKeys.scalesHeadPalmistry, preloadKey: ELottieKeys.scalesHeadPalmistry,
}); });
const { isReady, variant: zodiacImages } = useUnleash({
flag: EUnleashFlags.zodiacImages
});
const handleNext = () => { const handleNext = () => {
navigate(routes.client.compatibilityV2RelationshipStatus()); navigate(routes.client.compatibilityV2RelationshipStatus());
}; };
if (!isReady) {
return <Loader color={LoaderColor.Black} />;
}
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
{/* {animationData && ( {/* {animationData && (
@ -39,13 +49,25 @@ function PalmsInformation() {
width={1920} width={1920}
/> />
)} */} )} */}
<div className={styles.zodiac}> {zodiacImages !== "new" && (
<img <div className={styles.zodiac}>
className={styles.image} <img
src={images(`zodiacs/${gender}/${zodiacSign.toUpperCase()}.webp`)} className={styles.image}
alt="Zodiac sign" src={images(`zodiacs/${gender}/${zodiacSign.toUpperCase()}.webp`)}
/> alt="Zodiac sign"
</div> />
</div>
)}
{zodiacImages === "new" && (
<div className={styles.zodiacNew}>
<img
className={styles.image}
// src={images(`zodiacs/${gender}/${zodiacSign.toUpperCase()}.webp`)}
src={`/zodiac-signs/${gender?.toLowerCase()}/${zodiacSign.toLowerCase()}.svg`}
alt="Zodiac sign"
/>
</div>
)}
<Title variant="h2" className={styles.title}> <Title variant="h2" className={styles.title}>
{translate(`/palms-information.${zodiacSign.toLowerCase()}.title`)} {translate(`/palms-information.${zodiacSign.toLowerCase()}.title`)}
</Title> </Title>

View File

@ -57,3 +57,18 @@
white-space: pre-line; white-space: pre-line;
margin-bottom: 24px; margin-bottom: 24px;
} }
.zodiacNew {
position: relative;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
& > img {
height: 100%;
position: relative;
max-width: 260px;
z-index: -2;
}
}

View File

@ -11,6 +11,8 @@ import { selectors } from "@/store";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { images } from "../../data"; import { images } from "../../data";
import { getZodiacSignByDate } from "@/services/zodiac-sign"; import { getZodiacSignByDate } from "@/services/zodiac-sign";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import Loader, { LoaderColor } from "@/components/Loader";
function PalmsInformationPartner() { function PalmsInformationPartner() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -24,10 +26,18 @@ function PalmsInformationPartner() {
preloadKey: ELottieKeys.scalesHeadPalmistry, preloadKey: ELottieKeys.scalesHeadPalmistry,
}); });
const { isReady, variant: zodiacImages } = useUnleash({
flag: EUnleashFlags.zodiacImages
});
const handleNext = () => { const handleNext = () => {
navigate(routes.client.compatibilityV2DateEvent()); navigate(routes.client.compatibilityV2DateEvent());
}; };
if (!isReady) {
return <Loader color={LoaderColor.Black} />;
}
return ( return (
<div className={styles["page-container"]}> <div className={styles["page-container"]}>
{/* {animationData && ( {/* {animationData && (
@ -39,13 +49,25 @@ function PalmsInformationPartner() {
width={1920} width={1920}
/> />
)} */} )} */}
<div className={styles.zodiac}> {zodiacImages !== "new" && (
<img <div className={styles.zodiac}>
className={styles.image} <img
src={images(`zodiacs/${partnerGender}/${zodiacSign?.toUpperCase()}.webp`)} className={styles.image}
alt="Zodiac sign" src={images(`zodiacs/${partnerGender}/${zodiacSign.toUpperCase()}.webp`)}
/> alt="Zodiac sign"
</div> />
</div>
)}
{zodiacImages === "new" && (
<div className={styles.zodiacNew}>
<img
className={styles.image}
// src={images(`zodiacs/${gender}/${zodiacSign.toUpperCase()}.webp`)}
src={`/zodiac-signs/${partnerGender?.toLowerCase()}/${zodiacSign.toLowerCase()}.svg`}
alt="Zodiac sign"
/>
</div>
)}
<Title variant="h2" className={styles.title}> <Title variant="h2" className={styles.title}>
{translate(`/palms-information-partner.${zodiacSign?.toLowerCase()}.title`)} {translate(`/palms-information-partner.${zodiacSign?.toLowerCase()}.title`)}
</Title> </Title>

View File

@ -57,3 +57,18 @@
white-space: pre-line; white-space: pre-line;
margin-bottom: 24px; margin-bottom: 24px;
} }
.zodiacNew {
position: relative;
width: 100%;
display: flex;
justify-content: center;
align-items: center;
& > img {
height: 100%;
position: relative;
max-width: 260px;
z-index: -2;
}
}

View File

@ -0,0 +1,39 @@
import styles from "./styles.module.scss"
interface ZodiacImagesProps {
gender: string;
partnerGender?: string;
zodiacSign: string;
partnerZodiacSign?: string;
relationshipStatus: string;
classNameContainer?: string;
}
function ZodiacImages({
gender,
partnerGender,
zodiacSign,
partnerZodiacSign,
relationshipStatus,
classNameContainer = ""
}: ZodiacImagesProps) {
const getZodiacImagesWithPartnerClassName = () => {
if (relationshipStatus === "single") {
return "";
}
return `${styles["with-partner"]} ${styles[`with-partner-${gender}-${partnerGender}`]}`;
}
return (
<div
className={`${styles["zodiac-images"]} ${getZodiacImagesWithPartnerClassName()} ${classNameContainer}`}
// style={{ marginBottom: `${-height / 2}px` }}
>
<img src={`/zodiac-signs/${gender?.toLowerCase()}/${zodiacSign?.toLowerCase()}.svg`} alt="Profile zodiac sign" />
{relationshipStatus !== "single" && <img src={`/zodiac-signs/${partnerGender?.toLowerCase()}/${partnerZodiacSign?.toLowerCase()}.svg`} alt="Partner zodiac sign" />}
</div>
)
}
export default ZodiacImages

View File

@ -0,0 +1,66 @@
.zodiac-images {
position: relative;
width: 100dvw;
max-width: 560px;
display: flex;
justify-content: space-between;
align-items: center;
gap: 0px;
margin-top: 8px;
&.with-partner {
// &>img:first-child {
// margin-right: -30%;
// }
&>img:last-child {
// margin-left: -10px;
z-index: 2;
}
&>img {
width: 50%;
}
}
img {
position: relative;
width: 70%;
object-fit: cover;
z-index: 3;
}
&.with-partner-male-female {
flex-direction: row-reverse;
}
&.with-partner-male-male {
&>img:first-child {
transform: scaleX(-1);
}
}
&.with-partner-female-female {
&>img:last-child {
transform: scaleX(-1);
}
}
// &::after {
// content: "";
// position: absolute;
// bottom: 0;
// left: 0;
// width: 100%;
// height: 60%;
// background: linear-gradient(to bottom,
// rgba(255, 255, 255, 0) 0%,
// rgba(255, 255, 255, .7) 10%,
// rgba(255, 255, 255, 1) 15%,
// rgba(255, 255, 255, 1) 30%,
// rgba(255, 255, 255, 1) 40%,
// rgba(255, 255, 255, 1) 100%);
// pointer-events: none;
// z-index: -1;
// }
}

View File

@ -24,6 +24,9 @@ import { formatDateToLocale } from "@/locales/localFormats";
import { useEffect } from "react"; import { useEffect } from "react";
import metricService, { EGoals, EMetrics } from "@/services/metric/metricService"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService";
import MoneyBackGuarantee from "../../components/MoneyBackGuarantee"; import MoneyBackGuarantee from "../../components/MoneyBackGuarantee";
import ZodiacImages from "./components/ZodiacImages";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
import Loader, { LoaderColor } from "@/components/Loader";
function TrialPayment() { function TrialPayment() {
const { height, elementRef } = useDynamicSize<HTMLDivElement>({}); const { height, elementRef } = useDynamicSize<HTMLDivElement>({});
@ -41,6 +44,10 @@ function TrialPayment() {
"/v1/palmistry/ticket.svg", "/v1/palmistry/ticket.svg",
]) ])
const { isReady, variant: zodiacImages } = useUnleash({
flag: EUnleashFlags.zodiacImages
});
const handleNext = () => { const handleNext = () => {
navigate(routes.client.compatibilityV2Payment()); navigate(routes.client.compatibilityV2Payment());
}; };
@ -50,19 +57,31 @@ function TrialPayment() {
metricService.reachGoal(EGoals.AURA_TRIAL_PAYMENT_PAGE_VISIT, [EMetrics.KLAVIYO]); metricService.reachGoal(EGoals.AURA_TRIAL_PAYMENT_PAGE_VISIT, [EMetrics.KLAVIYO]);
}, []); }, []);
if (!isReady) {
return <Loader color={LoaderColor.Black} />;
}
return ( return (
<> <>
<Title className={styles["information-title"]}> <Title className={styles["information-title"]}>
{translate("/trial-payment.information-title")} {translate("/trial-payment.information-title")}
</Title> </Title>
<div
{zodiacImages === "new" && <ZodiacImages
gender={gender}
zodiacSign={zodiacSign}
relationshipStatus={relationshipStatus}
partnerGender={partnerGender}
partnerZodiacSign={partnerZodiacSign}
/>}
{zodiacImages !== "new" && <div
className={`${styles["zodiac-images"]} ${relationshipStatus !== "single" ? styles["with-partner"] : ""}`} className={`${styles["zodiac-images"]} ${relationshipStatus !== "single" ? styles["with-partner"] : ""}`}
ref={elementRef} ref={elementRef}
style={{ marginBottom: `${-height / 2}px` }} style={{ marginBottom: `${-height / 2}px` }}
> >
<img src={images(`zodiacs/${gender}/${zodiacSign?.toUpperCase()}.webp`)} alt="Profile zodiac sign" /> <img src={images(`zodiacs/${gender}/${zodiacSign?.toUpperCase()}.webp`)} alt="Profile zodiac sign" />
{relationshipStatus !== "single" && <img src={images(`zodiacs/${partnerGender}/${partnerZodiacSign?.toUpperCase()}.webp`)} alt="Partner zodiac sign" />} {relationshipStatus !== "single" && <img src={images(`zodiacs/${partnerGender}/${partnerZodiacSign?.toUpperCase()}.webp`)} alt="Partner zodiac sign" />}
</div> </div>}
{(relationshipStatus === "single" || !partnerBirthdate) && {(relationshipStatus === "single" || !partnerBirthdate) &&
<p className={styles["information-description"]}> <p className={styles["information-description"]}>
{translate("/trial-payment.information-description-single", { {translate("/trial-payment.information-description-single", {

View File

@ -1,25 +1,51 @@
import { useFlagsStatus, useUnleashClient, useVariant } from "@unleash/proxy-client-react"; import { useFlagsStatus, useUnleashClient, useVariant } from "@unleash/proxy-client-react";
import { useEffect, useMemo } from "react"; import { useEffect, useMemo } from "react";
import { useSearchParams } from "react-router-dom";
interface IUseUnleashProps { export enum EUnleashFlags {
flag: string; "genderPageType" = "genderPageType",
"zodiacImages" = "zodiacImages",
} }
export const useUnleash = ({ /**
* Интерфейс для входных параметров хука useUnleash
* Использует дженерик T для типизации флага
*/
interface IUseUnleashProps<T extends EUnleashFlags> {
flag: T;
}
/**
* Интерфейс для возможных вариантов значений флагов
* Каждый ключ соответствует флагу из EUnleashFlags
*/
interface IVariants {
[EUnleashFlags.genderPageType]: "v0" | "v1" | "v2";
[EUnleashFlags.zodiacImages]: "new" | "old";
}
/**
* Хук для получения значения A/B теста по флагу
* @template T - Тип флага из EUnleashFlags
* @returns Объект с информацией о готовности флага и его значении, типизированным в зависимости от переданного флага
*/
export const useUnleash = <T extends EUnleashFlags>({
flag flag
}: IUseUnleashProps) => { }: IUseUnleashProps<T>) => {
const { flagsReady } = useFlagsStatus(); const { flagsReady } = useFlagsStatus();
const unleashClient = useUnleashClient(); const unleashClient = useUnleashClient();
const abVariant = useVariant(flag); const abVariant = useVariant(flag);
// const isEnabled = useFlag(flag); // const isEnabled = useFlag(flag);
const [searchParams] = useSearchParams();
const variantFromParams = searchParams.get(flag);
const isReady = useMemo(() => { const isReady = useMemo(() => {
return flagsReady ?? true; return flagsReady ?? true;
}, [flagsReady]); }, [flagsReady]);
const variant = useMemo(() => { const variant = useMemo(() => {
return abVariant?.payload?.value; return variantFromParams || abVariant?.payload?.value;
}, [abVariant]); }, [abVariant, variantFromParams]) as IVariants[T];
useEffect(() => { useEffect(() => {
unleashClient.on("impression", (impressionEvent: any) => { unleashClient.on("impression", (impressionEvent: any) => {
@ -43,5 +69,5 @@ export const useUnleash = ({
}), [ }), [
isReady, isReady,
variant variant
]) ]) as { isReady: boolean; variant: IVariants[T] };
}; };