diff --git a/src/components/CompatibilityV2/pages/Gender/index.tsx b/src/components/CompatibilityV2/pages/Gender/index.tsx index 46b0bf0..d094d25 100644 --- a/src/components/CompatibilityV2/pages/Gender/index.tsx +++ b/src/components/CompatibilityV2/pages/Gender/index.tsx @@ -1,9 +1,7 @@ import styles from "./styles.module.scss"; -import Title from "@/components/Title"; import { useDispatch, useSelector } from "react-redux"; import { actions, selectors } from "@/store"; import { Gender } from "@/data"; -import PrivacyPolicy from "@/components/pages/ABDesign/v1/components/PrivacyPolicy"; // import Toast from "@/components/pages/ABDesign/v1/components/Toast"; import { useTranslations } from "@/hooks/translations"; import { ELocalesPlacement } from "@/locales"; @@ -17,10 +15,11 @@ import { usePreloadImages } from "@/hooks/preload/images"; import { ELottieKeys, useLottie } from "@/hooks/lottie/useLottie"; import { useSession } from "@/hooks/session/useSession"; 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 { 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() { const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); @@ -41,7 +40,7 @@ function GenderPage() { const { flags, ready } = useMetricABFlags(); const { isReady, variant: genderPageType } = useUnleash({ - flag: "genderPageType" + flag: EUnleashFlags.genderPageType }); const pageType = flags?.genderPageType?.[0] || genderPageType || "v2"; @@ -115,142 +114,19 @@ function GenderPage() { switch (pageType) { case "v0": return ( - <> - - {translate("/gender.title")} - -

{translate("/gender.description", { - br:
, - })}

- {/* */} - -
- {localGenders.map((_gender, index) => ( - selectGender(genders.find((g) => g.id === _gender.id) ?? null)} - /> - ))} -
- - {/* {gender && !privacyPolicyChecked && ( - - {translate("/gender.toast", undefined, ELocalesPlacement.V1)} - - )} */} - + ) case "v1": return ( - <> - - {translate("/gender.v1.title", { - br: <br />, - })} - -

{translate("/gender.v1.subtitle", { - br:
, - })}

-
    - {Array.from({ length: 4 }).map((_, index) => ( -
  • - {translate(`/gender.v1.points.point${index + 1}`)} -
  • - ))} -
- {/* */} -
- {localGenders.map((_gender, index) => ( - selectGender(genders.find((g) => g.id === _gender.id) ?? null)} - /> - ))} -
- - - {/* {gender && !privacyPolicyChecked && ( - - {translate("/gender.toast", undefined, ELocalesPlacement.V1)} - - )} */} - + ) case "v2": return ( - <> - - {translate("/gender.v2.title", { - br: <br />, - })} - -
    - {Array.from({ length: 5 }).map((_, index) => ( -
  • - {translate(`/gender.v2.points.point${index + 1}`)} -
  • - ))} -
-

{translate("/gender.v2.subtitle", { - br:
, - })}

- {/* */} -
- {localGenders.map((_gender, index) => ( - selectGender(genders.find((g) => g.id === _gender.id) ?? null)} - /> - ))} -
- - - {/* {gender && !privacyPolicyChecked && ( - - {translate("/gender.toast", undefined, ELocalesPlacement.V1)} - - )} */} - + ) default: return ( - <> - - {translate("/gender.title")} - -

{translate("/gender.description", { - br:
, - })}

- {/* */} - -
- {localGenders.map((_gender, index) => ( - selectGender(genders.find((g) => g.id === _gender.id) ?? null)} - /> - ))} -
- - {/* {gender && !privacyPolicyChecked && ( - - {translate("/gender.toast", undefined, ELocalesPlacement.V1)} - - )} */} - + ); } diff --git a/src/components/CompatibilityV2/pages/Gender/variants/GenderV0/index.tsx b/src/components/CompatibilityV2/pages/Gender/variants/GenderV0/index.tsx new file mode 100644 index 0000000..87a3221 --- /dev/null +++ b/src/components/CompatibilityV2/pages/Gender/variants/GenderV0/index.tsx @@ -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 ( + <> + + {translate("/gender.title")} + +

{translate("/gender.description", { + br:
, + })}

+ {/* */} + +
+ {localGenders.map((_gender, index) => ( + selectGender(genders.find((g) => g.id === _gender.id) ?? null)} + /> + ))} +
+ + {/* {gender && !privacyPolicyChecked && ( + + {translate("/gender.toast", undefined, ELocalesPlacement.V1)} + + )} */} + + ) +} + +export default GenderV0 \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/Gender/variants/GenderV1/index.tsx b/src/components/CompatibilityV2/pages/Gender/variants/GenderV1/index.tsx new file mode 100644 index 0000000..d681987 --- /dev/null +++ b/src/components/CompatibilityV2/pages/Gender/variants/GenderV1/index.tsx @@ -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 ( + <> + + {translate("/gender.v1.title", { + br: <br />, + })} + +

{translate("/gender.v1.subtitle", { + br:
, + })}

+
    + {Array.from({ length: 4 }).map((_, index) => ( +
  • + {translate(`/gender.v1.points.point${index + 1}`)} +
  • + ))} +
+ {/* */} +
+ {localGenders.map((_gender, index) => ( + selectGender(genders.find((g) => g.id === _gender.id) ?? null)} + /> + ))} +
+ + + {/* {gender && !privacyPolicyChecked && ( + + {translate("/gender.toast", undefined, ELocalesPlacement.V1)} + + )} */} + + ) +} + +export default GenderV1 \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/Gender/variants/GenderV2/index.tsx b/src/components/CompatibilityV2/pages/Gender/variants/GenderV2/index.tsx new file mode 100644 index 0000000..6595f4b --- /dev/null +++ b/src/components/CompatibilityV2/pages/Gender/variants/GenderV2/index.tsx @@ -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 ( + <> + + {translate("/gender.v2.title", { + br: <br />, + })} + +
    + {Array.from({ length: 5 }).map((_, index) => ( +
  • + {translate(`/gender.v2.points.point${index + 1}`)} +
  • + ))} +
+

{translate("/gender.v2.subtitle", { + br:
, + })}

+ {/* */} +
+ {localGenders.map((_gender, index) => ( + selectGender(genders.find((g) => g.id === _gender.id) ?? null)} + /> + ))} +
+ + + {/* {gender && !privacyPolicyChecked && ( + + {translate("/gender.toast", undefined, ELocalesPlacement.V1)} + + )} */} + + ) +} + +export default GenderV2 \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/PalmsInformation/index.tsx b/src/components/CompatibilityV2/pages/PalmsInformation/index.tsx index 7f6f85d..872f829 100644 --- a/src/components/CompatibilityV2/pages/PalmsInformation/index.tsx +++ b/src/components/CompatibilityV2/pages/PalmsInformation/index.tsx @@ -12,6 +12,8 @@ import { selectors } from "@/store"; import { useSelector } from "react-redux"; import { images } from "../../data"; import { getZodiacSignByDate } from "@/services/zodiac-sign"; +import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash"; +import Loader, { LoaderColor } from "@/components/Loader"; function PalmsInformation() { const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); @@ -24,10 +26,18 @@ function PalmsInformation() { preloadKey: ELottieKeys.scalesHeadPalmistry, }); + const { isReady, variant: zodiacImages } = useUnleash({ + flag: EUnleashFlags.zodiacImages + }); + const handleNext = () => { navigate(routes.client.compatibilityV2RelationshipStatus()); }; + if (!isReady) { + return ; + } + return (
{/* {animationData && ( @@ -39,13 +49,25 @@ function PalmsInformation() { width={1920} /> )} */} -
- Zodiac sign -
+ {zodiacImages !== "new" && ( +
+ Zodiac sign +
+ )} + {zodiacImages === "new" && ( +
+ Zodiac sign +
+ )} {translate(`/palms-information.${zodiacSign.toLowerCase()}.title`)} diff --git a/src/components/CompatibilityV2/pages/PalmsInformation/styles.module.scss b/src/components/CompatibilityV2/pages/PalmsInformation/styles.module.scss index bd40adf..650127f 100644 --- a/src/components/CompatibilityV2/pages/PalmsInformation/styles.module.scss +++ b/src/components/CompatibilityV2/pages/PalmsInformation/styles.module.scss @@ -56,4 +56,19 @@ .description { white-space: pre-line; 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; + } } \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/PalmsInformationPartner/index.tsx b/src/components/CompatibilityV2/pages/PalmsInformationPartner/index.tsx index b493506..92b131d 100644 --- a/src/components/CompatibilityV2/pages/PalmsInformationPartner/index.tsx +++ b/src/components/CompatibilityV2/pages/PalmsInformationPartner/index.tsx @@ -11,11 +11,13 @@ import { selectors } from "@/store"; import { useSelector } from "react-redux"; import { images } from "../../data"; import { getZodiacSignByDate } from "@/services/zodiac-sign"; +import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash"; +import Loader, { LoaderColor } from "@/components/Loader"; function PalmsInformationPartner() { const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2); const { partnerGender, partnerBirthdate } = useSelector(selectors.selectQuestionnaire); - + const zodiacSign = getZodiacSignByDate(partnerBirthdate); const navigate = useNavigate(); // const { animationData } = @@ -24,10 +26,18 @@ function PalmsInformationPartner() { preloadKey: ELottieKeys.scalesHeadPalmistry, }); + const { isReady, variant: zodiacImages } = useUnleash({ + flag: EUnleashFlags.zodiacImages + }); + const handleNext = () => { navigate(routes.client.compatibilityV2DateEvent()); }; + if (!isReady) { + return ; + } + return (
{/* {animationData && ( @@ -39,13 +49,25 @@ function PalmsInformationPartner() { width={1920} /> )} */} -
- Zodiac sign -
+ {zodiacImages !== "new" && ( +
+ Zodiac sign +
+ )} + {zodiacImages === "new" && ( +
+ Zodiac sign +
+ )} {translate(`/palms-information-partner.${zodiacSign?.toLowerCase()}.title`)} diff --git a/src/components/CompatibilityV2/pages/PalmsInformationPartner/styles.module.scss b/src/components/CompatibilityV2/pages/PalmsInformationPartner/styles.module.scss index bd40adf..650127f 100644 --- a/src/components/CompatibilityV2/pages/PalmsInformationPartner/styles.module.scss +++ b/src/components/CompatibilityV2/pages/PalmsInformationPartner/styles.module.scss @@ -56,4 +56,19 @@ .description { white-space: pre-line; 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; + } } \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/index.tsx b/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/index.tsx new file mode 100644 index 0000000..af16b69 --- /dev/null +++ b/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/index.tsx @@ -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 ( +
+ Profile zodiac sign + {relationshipStatus !== "single" && Partner zodiac sign} +
+ ) +} + +export default ZodiacImages \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/styles.module.scss b/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/styles.module.scss new file mode 100644 index 0000000..36ecaad --- /dev/null +++ b/src/components/CompatibilityV2/pages/TrialPayment/components/ZodiacImages/styles.module.scss @@ -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; + // } +} \ No newline at end of file diff --git a/src/components/CompatibilityV2/pages/TrialPayment/index.tsx b/src/components/CompatibilityV2/pages/TrialPayment/index.tsx index 1ef00b2..8f23bfc 100644 --- a/src/components/CompatibilityV2/pages/TrialPayment/index.tsx +++ b/src/components/CompatibilityV2/pages/TrialPayment/index.tsx @@ -24,6 +24,9 @@ import { formatDateToLocale } from "@/locales/localFormats"; import { useEffect } from "react"; import metricService, { EGoals, EMetrics } from "@/services/metric/metricService"; 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() { const { height, elementRef } = useDynamicSize({}); @@ -41,6 +44,10 @@ function TrialPayment() { "/v1/palmistry/ticket.svg", ]) + const { isReady, variant: zodiacImages } = useUnleash({ + flag: EUnleashFlags.zodiacImages + }); + const handleNext = () => { navigate(routes.client.compatibilityV2Payment()); }; @@ -50,19 +57,31 @@ function TrialPayment() { metricService.reachGoal(EGoals.AURA_TRIAL_PAYMENT_PAGE_VISIT, [EMetrics.KLAVIYO]); }, []); + if (!isReady) { + return ; + } + return ( <> {translate("/trial-payment.information-title")} -
} + {zodiacImages !== "new" &&
Profile zodiac sign {relationshipStatus !== "single" && Partner zodiac sign} -
+
} {(relationshipStatus === "single" || !partnerBirthdate) &&

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