AW-483-484-485-fix-bugs

This commit is contained in:
Daniil Chemerkin 2025-09-12 15:46:08 +00:00
parent 4b7332f4d5
commit 5cc17824f3
16 changed files with 385 additions and 92 deletions

View File

@ -664,6 +664,12 @@
"title": "Bought today: <count>",
"description": "<count> people are buying now:"
}
},
"price-descriptions": {
"1": "Basic",
"2": "Standard",
"3": "Popular",
"4": "Premium"
}
},
"/scan-hand": {

View File

@ -7,6 +7,20 @@
"day": "Day",
"year": "Year",
"month": "Month",
"months": {
"january": "January",
"february": "February",
"march": "March",
"april": "April",
"may": "May",
"june": "June",
"july": "July",
"august": "August",
"september": "September",
"october": "October",
"november": "November",
"december": "December"
},
"aura_paywall_redesign_main": {
"text_0": "We've helped millions of people to have happier lives and better relationships, and we want to help you too.",
"text_0_color": "millions",

View File

@ -14,6 +14,7 @@ import { useSession } from "@/hooks/session/useSession";
import { ESourceAuthorization } from "@/api/resources/User";
import { usePreloadImages } from "@/hooks/preload/images";
import { getZodiacSignByDate, ZODIACS } from "@/services/zodiac-sign";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
function Birthdate() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -26,14 +27,19 @@ function Birthdate() {
const { gender } = useSelector(selectors.selectQuestionnaire);
const zodiacSign = getZodiacSignByDate(birthdate);
const { variant: dateMonth = "v0" } = useUnleash({
flag: EUnleashFlags.v2CompatibilityDateMonth,
});
usePreloadImages(
!!zodiacSign?.length ?
[
`/zodiac-signs/${gender?.toLowerCase()}/${zodiacSign?.toLowerCase()}.svg`
] :
ZODIACS.map(
(zodiac) => `/zodiac-signs/${gender?.toLowerCase()}/${zodiac?.toLowerCase()}.svg`
)
zodiacSign?.length
? [
`/zodiac-signs/${gender?.toLowerCase()}/${zodiacSign?.toLowerCase()}.svg`,
]
: ZODIACS.map(
(zodiac) =>
`/zodiac-signs/${gender?.toLowerCase()}/${zodiac?.toLowerCase()}.svg`
)
);
const handleValid = (_birthdate: string) => {
@ -82,6 +88,7 @@ function Birthdate() {
onValid={handleValid}
onInvalid={() => setIsDisabled(true)}
inputClassName="date-picker-input"
monthsDisplay={dateMonth === "v1" ? "name" : "number"}
/>
<Button
className={styles.button}

View File

@ -13,6 +13,7 @@ import { useSession } from "@/hooks/session/useSession";
import { ESourceAuthorization } from "@/api/resources/User";
import { usePreloadImages } from "@/hooks/preload/images";
import { getZodiacSignByDate, ZODIACS } from "@/services/zodiac-sign";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
function BirthdatePartner() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -24,6 +25,10 @@ function BirthdatePartner() {
const [isDisabled, setIsDisabled] = useState(true);
const zodiacSign = getZodiacSignByDate(birthdate);
const { variant: dateMonth = "v0" } = useUnleash({
flag: EUnleashFlags.v2CompatibilityDateMonth,
});
usePreloadImages(
!!zodiacSign?.length ?
[
@ -64,6 +69,7 @@ function BirthdatePartner() {
onValid={handleValid}
onInvalid={() => setIsDisabled(true)}
inputClassName="date-picker-input"
monthsDisplay={dateMonth === "v1" ? "name" : "number"}
/>
<Button
className={styles.button}

View File

@ -11,6 +11,7 @@ import { useTranslations } from "@/hooks/translations";
import { ELocalesPlacement } from "@/locales";
import { useSession } from "@/hooks/session/useSession";
import { ESourceAuthorization } from "@/api/resources/User";
import { EUnleashFlags, useUnleash } from "@/hooks/ab/unleash/useUnleash";
function DateEvent() {
const { translate } = useTranslations(ELocalesPlacement.CompatibilityV2);
@ -23,6 +24,10 @@ function DateEvent() {
const [dateEvent, setDateEvent] = useState(dateEventFromStore);
const [isDisabled, setIsDisabled] = useState(true);
const { variant: dateMonth = "v0" } = useUnleash({
flag: EUnleashFlags.v2CompatibilityDateMonth,
});
const handleValid = (_birthdate: string) => {
setDateEvent(_birthdate);
setIsDisabled(_birthdate === "");
@ -54,6 +59,7 @@ function DateEvent() {
onInvalid={() => setIsDisabled(true)}
inputClassName="date-picker-input"
differenceOfMaximumAndCurrentYear={0}
monthsDisplay={dateMonth === "v1" ? "name" : "number"}
/>
<Button
className={styles.button}

View File

@ -116,7 +116,7 @@ function TrialChoice() {
};
const handleNext = () => {
if (isDisabled && trialButtonVariant === "v1") {
if (isDisabled && ["v1", "v2", "v3", "v4"].includes(trialButtonVariant)) {
setIsDisabledButtonClicked(true);
bottomRef.current?.scrollIntoView({
@ -203,37 +203,48 @@ function TrialChoice() {
<div className={`${styles["price-container"]}`}>
<div
className={`${styles.priceListContainer} ${
isDisabledButtonClicked && isDisabled
isDisabledButtonClicked &&
isDisabled &&
["v1", "v2"].includes(trialButtonVariant)
? styles.priceListContainerDisabled
: ""
}`}
>
{isDisabledButtonClicked && isDisabled && (
<div className={styles.priceListContainerDisabledText}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="16" height="16" rx="8" fill="#EF4444" />
<path
d="M8.75 3C8.75 2.58516 8.41484 2.25 8 2.25C7.58516 2.25 7.25 2.58516 7.25 3V9C7.25 9.41484 7.58516 9.75 8 9.75C8.41484 9.75 8.75 9.41484 8.75 9V3ZM8 12.75C8.24864 12.75 8.4871 12.6512 8.66291 12.4754C8.83873 12.2996 8.9375 12.0611 8.9375 11.8125C8.9375 11.5639 8.83873 11.3254 8.66291 11.1496C8.4871 10.9738 8.24864 10.875 8 10.875C7.75136 10.875 7.5129 10.9738 7.33709 11.1496C7.16127 11.3254 7.0625 11.5639 7.0625 11.8125C7.0625 12.0611 7.16127 12.2996 7.33709 12.4754C7.5129 12.6512 7.75136 12.75 8 12.75Z"
fill="white"
/>
</svg>
{isDisabledButtonClicked &&
isDisabled &&
["v1", "v2"].includes(trialButtonVariant) && (
<div className={styles.priceListContainerDisabledText}>
<svg
width="16"
height="16"
viewBox="0 0 16 16"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<rect width="16" height="16" rx="8" fill="#EF4444" />
<path
d="M8.75 3C8.75 2.58516 8.41484 2.25 8 2.25C7.58516 2.25 7.25 2.58516 7.25 3V9C7.25 9.41484 7.58516 9.75 8 9.75C8.41484 9.75 8.75 9.41484 8.75 9V3ZM8 12.75C8.24864 12.75 8.4871 12.6512 8.66291 12.4754C8.83873 12.2996 8.9375 12.0611 8.9375 11.8125C8.9375 11.5639 8.83873 11.3254 8.66291 11.1496C8.4871 10.9738 8.24864 10.875 8 10.875C7.75136 10.875 7.5129 10.9738 7.33709 11.1496C7.16127 11.3254 7.0625 11.5639 7.0625 11.8125C7.0625 12.0611 7.16127 12.2996 7.33709 12.4754C7.5129 12.6512 7.75136 12.75 8 12.75Z"
fill="white"
/>
</svg>
{translate("/trial-choice.please-select")}
</div>
)}
{translate("/trial-choice.please-select")}
</div>
)}
<PriceList
products={products}
activeItem={selectedPrice}
classNameItem={styles["price-item"]}
classNameItem={`${styles["price-item"]} ${
styles[trialButtonVariant]
} ${
isDisabledButtonClicked && isDisabled
? styles.priceItemDisabled
: ""
}`}
classNameItemActive={`${styles["price-item-active"]}`}
classNamePricesContainer={styles["prices-container"]}
classNameDescription={styles["price-item-description"]}
currency={currency}
showArrow={true}
productWidthArrow={products[products.length - 1]}
@ -253,6 +264,17 @@ function TrialChoice() {
</svg>
}
click={handlePriceItem}
displayMode={["v2", "v4"].includes(trialButtonVariant) ? "grid" : "row"}
pricesDescription={
["v2", "v4"].includes(trialButtonVariant)
? [
translate("/trial-choice.price-descriptions.1"),
translate("/trial-choice.price-descriptions.2"),
translate("/trial-choice.price-descriptions.3"),
translate("/trial-choice.price-descriptions.4"),
]
: []
}
/>
</div>
<p className={styles["auxiliary-text"]}>
@ -268,13 +290,17 @@ function TrialChoice() {
<Button
className={`${styles.button} ${
trialButtonVariant === "v1" ? styles["button-v1"] : ""
["v1", "v2", "v3", "v4"].includes(trialButtonVariant)
? styles["button-v1"]
: ""
} ${
isDisabled && trialButtonVariant === "v1"
isDisabled && ["v1", "v2", "v3", "v4"].includes(trialButtonVariant)
? styles["button-v1-disabled"]
: ""
}`}
disabled={isDisabled && trialButtonVariant !== "v1"}
disabled={
isDisabled && !["v1", "v2", "v3", "v4"].includes(trialButtonVariant)
}
onClick={handleNext}
>
{translate("next")}

View File

@ -65,6 +65,54 @@
color: #fb6c6c;
border-color: #fb6c6c;
}
&.v1,
&.v3 {
border: 1px solid #9ca3af;
&.price-item-active {
background: linear-gradient(90deg, #5393de 0%, #3a70b2 100%);
color: #fff !important;
border: none !important;
}
&:last-child {
border-width: 3px;
}
}
&.v2,
&.v4 {
font-weight: 700;
border: 2px solid #d1d5db;
border-radius: 12px;
&.price-item-active {
background: linear-gradient(90deg, #5393de 0%, #3a70b2 100%);
color: #fff !important;
border: 2px solid #3a70b2;
& > .price-item-description {
color: #fff;
}
}
&:last-child {
border-width: 4px;
}
}
&.v3,
&.v4 {
&.priceItemDisabled {
color: #fb6c6c;
border-color: #fb6c6c;
& > .price-item-description {
color: #fb6c6c;
}
}
}
}
:global(.dark-theme) .price-item {
@ -79,6 +127,52 @@
color: #fb6c6c;
border-color: #fb6c6c;
}
&.v1,
&.v3 {
border: 1px solid #9ca3af;
&.price-item-active {
background: linear-gradient(90deg, #5393de 0%, #3a70b2 100%);
color: #fff !important;
border: none !important;
}
&:last-child {
border-width: 3px;
}
}
&.v2,
&.v4 {
border: 2px solid #d1d5db;
&.price-item-active {
background: linear-gradient(90deg, #5393de 0%, #3a70b2 100%);
color: #fff !important;
border: 2px solid #3a70b2;
& > .price-item-description {
color: #fff;
}
}
&:last-child {
border-width: 4px;
}
}
&.v3,
&.v4 {
&.priceItemDisabled {
color: #fb6c6c;
border-color: #fb6c6c;
& > .price-item-description {
color: #fb6c6c;
}
}
}
}
.price-container {
@ -118,15 +212,11 @@
}
& .price-item {
border: 1px solid rgba(156, 163, 175, 1);
// border: 1px solid rgba(156, 163, 175, 1);
color: #000;
background-color: #fff;
box-shadow: 0px 4px 6px 0px rgba(0, 0, 0, 0.1),
0px 2px 4px 0px rgba(0, 0, 0, 0.1);
&:last-child {
border-width: 3px;
}
}
}
}

View File

@ -1,4 +1,5 @@
import { FormField } from "@/types";
import { MonthOption } from "@/utils/monthNames";
type DatePartValue = string | undefined;
@ -6,7 +7,7 @@ type DateInputProps = Omit<
FormField<DatePartValue>,
"onValid" | "onInvalid"
> & {
values: number[] | string[];
values: (number | string | MonthOption)[];
onChange: (part: string) => void;
};
@ -21,8 +22,8 @@ function DateInput(props: DateInputProps): JSX.Element {
onChange,
} = props;
const handleChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const datePart = e.target.value ? parseInt(e.target.value, 10) : 0;
onChange(String(datePart));
const selectedValue = e.target.value;
onChange(selectedValue);
};
return (
<div className={`date-picker__field ${inputClassName ?? ""}`}>
@ -37,11 +38,15 @@ function DateInput(props: DateInputProps): JSX.Element {
<option value="" hidden>
{placeholder}
</option>
{values.map((_value, index) => (
<option key={`${_value}_${index}`} value={_value}>
{_value}
</option>
))}
{values.map((_value, index) => {
const optionValue = typeof _value === 'object' ? _value.value : _value;
const optionLabel = typeof _value === 'object' ? _value.label : _value;
return (
<option key={`${optionValue}_${index}`} value={optionValue}>
{optionLabel}
</option>
);
})}
</select>
</div>
);

View File

@ -7,14 +7,24 @@ import { stringify, getCurrentYear } from "./utils";
import { IDate, getDateAsString } from "@/services/date";
import { ELocalesPlacement, language } from "@/locales";
import { getDateInputLocaleFormat } from "@/locales/localFormats";
import { getMonthOptions } from "@/utils/monthNames";
export function DatePicker(
props: FormField<Date | IDate | string> & {
differenceOfMaximumAndCurrentYear?: number;
monthsDisplay?: "number" | "name";
}
): JSX.Element {
const { translate } = useTranslations(ELocalesPlacement.V1);
const { name, value, inputClassName, differenceOfMaximumAndCurrentYear = 11, onValid, onInvalid } = props;
const {
name,
value,
inputClassName,
differenceOfMaximumAndCurrentYear = 11,
onValid,
onInvalid,
monthsDisplay = "number",
} = props;
const date = getDateAsString(value);
const [initYear, initMonth, initDay] = date.split("-");
const [year, setYear] = useState(initYear);
@ -31,12 +41,15 @@ export function DatePicker(
return Array.from({ length: 66 }, (_, index) => {
return currentYear - differenceOfMaximumAndCurrentYear - index;
});
}, []);
}, [differenceOfMaximumAndCurrentYear]);
const months = useMemo(() => {
if (monthsDisplay === "name") {
return getMonthOptions(translate);
}
return Array.from({ length: 12 }, (_, index) => {
return index + 1;
});
}, []);
}, [translate, monthsDisplay]);
const days = useMemo(() => {
return Array.from(

View File

@ -1,5 +1,5 @@
import { removeAfterDot, roundToWhole } from "@/services/price";
import styles from "./styles.module.css";
import styles from "./styles.module.scss";
import Price, { Currency, Locale } from "@/components/PaymentTable/Price";
import { useEffect, useRef } from "react";
import { useDispatch } from "react-redux";
@ -11,8 +11,10 @@ interface PriceItemProps {
active: boolean;
className?: string;
classNameActive?: string;
classNameDescription?: string;
currency: Currency;
arrowElement?: React.ReactNode;
description?: string;
click: (id: string) => void;
}
@ -22,8 +24,10 @@ function PriceItem({
active,
className = "",
classNameActive = "",
classNameDescription = "",
currency = Currency.USD,
arrowElement,
description,
click,
}: PriceItemProps): JSX.Element {
const dispatch = useDispatch();
@ -65,6 +69,11 @@ function PriceItem({
className={compatClassName()}
>
{removeAfterDot(_price.format())}
{!!description?.length && (
<p className={`${styles.description} ${classNameDescription}`}>
{description}
</p>
)}
{arrowElement}
</div>
);

View File

@ -1,40 +0,0 @@
.container {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.active {
background-color: #D1ACF2;
border-color: #D1ACF2 !important;
color: #fff;
}
.popular {
border-color: #30bf52;
}
.popular:after {
content: 'Most Popular';
position: absolute;
width: 80px;
height: 20px;
left: -10px;
bottom: -25px;
background-color: #30bf52;
font-size: 9px;
font-weight: 500;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
}

View File

@ -0,0 +1,58 @@
.container {
position: relative;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
width: 60px;
height: 60px;
border-radius: 6px;
font-weight: 600;
cursor: pointer;
transition: background-color 0.3s, color 0.3s;
}
.active {
background-color: #d1acf2;
border-color: #d1acf2 !important;
color: #fff;
}
.popular {
border-color: #30bf52;
}
.popular:after {
content: "Most Popular";
position: absolute;
width: 80px;
height: 20px;
left: -10px;
bottom: -25px;
background-color: #30bf52;
font-size: 9px;
font-weight: 500;
color: #fff;
display: flex;
justify-content: center;
align-items: center;
border-radius: 10px;
}
.description {
font-size: 14px;
font-weight: 600;
color: #6b7280;
}
:global(.dark-theme) {
.description {
color: #9ca3af;
}
.active {
& > .description {
color: #4f8de5;
}
}
}

View File

@ -12,11 +12,14 @@ interface PriceListProps {
classNameItem?: string;
classNameItemActive?: string;
classNamePricesContainer?: string;
classNameDescription?: string;
currency?: Currency;
preActiveItems?: string[];
showArrow?: boolean;
productWidthArrow?: IFunnelPaymentVariant;
arrowElement?: React.ReactNode;
displayMode?: "row" | "grid";
pricesDescription?: string[];
click: () => void;
}
@ -30,11 +33,14 @@ function PriceList({
classNameItem = "",
classNameItemActive = "",
classNamePricesContainer = "",
classNameDescription = "",
preActiveItems = [],
currency = Currency.USD,
showArrow = false,
productWidthArrow,
arrowElement,
displayMode = "row",
pricesDescription = [],
}: PriceListProps): JSX.Element {
const dispatch = useDispatch();
const [activeProductItem, setActiveProductItem] =
@ -54,7 +60,9 @@ function PriceList({
};
return (
<div className={`${styles.container} ${classNamePricesContainer}`}>
<div
className={`${styles.container} ${classNamePricesContainer} ${styles[displayMode]}`}
>
{products.map((product, idx) => (
<PriceItem
active={
@ -64,8 +72,9 @@ function PriceList({
key={idx}
value={getPrice(product)}
id={product.id}
className={classNameItem}
classNameActive={classNameItemActive}
className={`${classNameItem} ${styles.item} ${styles[displayMode]}`}
classNameActive={`${classNameItemActive} ${styles.itemActive} ${styles[displayMode]}`}
classNameDescription={classNameDescription}
click={priceItemClick}
currency={currency}
arrowElement={
@ -73,6 +82,7 @@ function PriceList({
? arrowElement
: null
}
description={pricesDescription[idx]}
/>
))}
</div>

View File

@ -4,4 +4,36 @@
flex-direction: row;
justify-content: space-between;
gap: 4px;
&.grid {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
}
.item {
&.grid {
width: 162px;
flex-direction: column;
gap: 8px;
height: 76px;
}
}
@media (max-width: 380px) {
.item {
&.grid {
width: 142px;
}
}
}
@media (max-width: 340px) {
.item {
&.grid {
width: 102px;
}
}
}

View File

@ -39,6 +39,7 @@ export enum EUnleashFlags {
"v2CompatibilityReviewPage" = "v2-compatibility-review-page",
"v2CompatibilityPathToEnteringBirthdate" = "v2-compatibility-path-to-entering-birthdate",
"v2CompatibilityTrialChoicePath" = "v2-compatibility-trial-choice-path",
"v2CompatibilityDateMonth" = "v2-compatibility-date-month",
}
interface IUseUnleashProps<T extends EUnleashFlags> {
@ -73,7 +74,7 @@ interface IVariants {
[EUnleashFlags.compatibilityV2TrialTextPrice]: "v0" | "v1" | "v2" | "v3";
[EUnleashFlags.compatibilityV3TrialTextPrice]: "v0" | "v1" | "v2" | "v3";
[EUnleashFlags.compatibilityV4TrialTextPrice]: "v0" | "v1" | "v2" | "v3";
[EUnleashFlags.compatibilityV2TrialButton]: "v0" | "v1";
[EUnleashFlags.compatibilityV2TrialButton]: "v0" | "v1" | "v2" | "v3" | "v4";
[EUnleashFlags.palmistryV1TrialTextPrice]: "v0" | "v1" | "v2" | "v3";
[EUnleashFlags.v2CompatibilityScanResultNumbers]: 'off' | 'on';
[EUnleashFlags.v2CompatibilityScanInstructionImage]: 'v0' | 'v1';
@ -82,6 +83,7 @@ interface IVariants {
[EUnleashFlags.v2CompatibilityReviewPage]: 'v0' | 'v1' | 'v2' | 'v3' | 'v4';
[EUnleashFlags.v2CompatibilityPathToEnteringBirthdate]: 'hide' | 'show';
[EUnleashFlags.v2CompatibilityTrialChoicePath]: 'v0' | 'v1' | 'v2' | 'v3';
[EUnleashFlags.v2CompatibilityDateMonth]: 'v0' | 'v1';
}
/**

49
src/utils/monthNames.ts Normal file
View File

@ -0,0 +1,49 @@
export interface MonthOption {
value: string;
label: string;
}
const monthKeys = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
];
export const getMonthOptions = (
translate: (key: string) => string
): MonthOption[] => {
return monthKeys.map((monthKey, index) => ({
value: String(index + 1),
label: translate(`months.${monthKey}`),
}));
};
export const getMonthName = (
monthNumber: string | number,
translate: (key: string) => string
): string => {
const index = Number(monthNumber) - 1;
const monthKey = monthKeys[index];
return monthKey ? translate(`months.${monthKey}`) : String(monthNumber);
};
export const getMonthNumber = (
monthName: string,
translate: (key: string) => string
): string => {
const index = monthKeys.findIndex(
(monthKey) => translate(`months.${monthKey}`) === monthName
);
return index !== -1 ? String(index + 1) : monthName;
};