From e8519615082369f3f90ecb7ef51ec7401dbd396e Mon Sep 17 00:00:00 2001 From: "dev.daminik00" Date: Thu, 9 Oct 2025 20:29:40 +0200 Subject: [PATCH] fix date select and link redirect --- .../domains/TrialPayment/Legal/Legal.tsx | 2 +- .../templates/DateTemplate/DateTemplate.tsx | 33 ++++++++++++------- .../TrialPaymentTemplate.tsx | 4 --- .../QuestionDateAnswers.stories.tsx | 6 ---- .../widgets/DateInput/DateInput.stories.tsx | 8 +---- .../widgets/DateInput/DateInput.tsx | 3 -- .../PrivacyTermsConsent.tsx | 4 +-- src/hooks/useDateInput.ts | 22 ++++++------- 8 files changed, 36 insertions(+), 46 deletions(-) diff --git a/src/components/domains/TrialPayment/Legal/Legal.tsx b/src/components/domains/TrialPayment/Legal/Legal.tsx index d21ba58..3247bca 100644 --- a/src/components/domains/TrialPayment/Legal/Legal.tsx +++ b/src/components/domains/TrialPayment/Legal/Legal.tsx @@ -45,7 +45,7 @@ export default function Legal({ link.className )} > - {link.children} + {link.children} ))} diff --git a/src/components/funnel/templates/DateTemplate/DateTemplate.tsx b/src/components/funnel/templates/DateTemplate/DateTemplate.tsx index a567150..5356e3a 100644 --- a/src/components/funnel/templates/DateTemplate/DateTemplate.tsx +++ b/src/components/funnel/templates/DateTemplate/DateTemplate.tsx @@ -9,18 +9,19 @@ import { TemplateLayout } from "../layouts/TemplateLayout"; import { createTemplateLayoutProps } from "@/lib/funnel/templateHelpers"; // Утилита для форматирования даты на основе паттерна -function formatDateByPattern(date: Date, pattern: string): string { +// Принимает год, месяц, день как числа (без зависимости от часовых зон) +function formatDateByPattern(year: number, month: number, day: number, pattern: string): string { const monthNames = [ "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" ]; return pattern - .replace("MMMM", monthNames[date.getMonth()]) - .replace("MMM", monthNames[date.getMonth()].substring(0, 3)) - .replace("yyyy", date.getFullYear().toString()) - .replace("dd", date.getDate().toString().padStart(2, '0')) - .replace("d", date.getDate().toString()); + .replace("MMMM", monthNames[month - 1]) + .replace("MMM", monthNames[month - 1].substring(0, 3)) + .replace("yyyy", year.toString()) + .replace("dd", day.toString().padStart(2, '0')) + .replace("d", day.toString()); } interface DateTemplateProps { @@ -83,12 +84,21 @@ export function DateTemplate({ // Форматированная дата для отображения const formattedDate = useMemo(() => { - if (!isoDate) return null; + const { month, day, year } = selectedDate; + if (!month || !day || !year) return null; - const date = new Date(isoDate); - const pattern = screen.dateInput?.selectedDateFormat || "MMMM d, yyyy"; - return formatDateByPattern(date, pattern); - }, [isoDate, screen.dateInput?.selectedDateFormat]); + const monthNum = parseInt(month); + const dayNum = parseInt(day); + const yearNum = parseInt(year); + + // Валидация даты + if (monthNum >= 1 && monthNum <= 12 && dayNum >= 1 && dayNum <= 31 && yearNum > 1900) { + const pattern = screen.dateInput?.selectedDateFormat || "MMMM d, yyyy"; + return formatDateByPattern(yearNum, monthNum, dayNum, pattern); + } + + return null; + }, [selectedDate, screen.dateInput?.selectedDateFormat]); // Компонент отображения выбранной даты над кнопкой const selectedDateDisplay = formattedDate && screen.dateInput?.showSelectedDate !== false ? ( @@ -138,7 +148,6 @@ export function DateTemplate({ onChange={handleDateChange} maxYear={new Date().getFullYear() - 11} yearsRange={100} - locale="en" /> {defaultTexts?.privacyBanner && ( diff --git a/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx b/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx index b074cb6..f387f1c 100644 --- a/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx +++ b/src/components/funnel/templates/TrialPaymentTemplate/TrialPaymentTemplate.tsx @@ -689,8 +689,6 @@ export function TrialPaymentTemplate({ By clicking Continue, you agree to our{" "} Terms of Use & Service @@ -698,8 +696,6 @@ export function TrialPaymentTemplate({ and{" "} Privacy Policy diff --git a/src/components/templates/QuestionDateAnswers/QuestionDateAnswers.stories.tsx b/src/components/templates/QuestionDateAnswers/QuestionDateAnswers.stories.tsx index ae2ac58..b6ba595 100644 --- a/src/components/templates/QuestionDateAnswers/QuestionDateAnswers.stories.tsx +++ b/src/components/templates/QuestionDateAnswers/QuestionDateAnswers.stories.tsx @@ -34,7 +34,6 @@ const meta: Meta = { onChange: fn(), maxYear: new Date().getFullYear() - 11, yearsRange: 100, - locale: "en", }, bottomActionButtonProps: { actionButtonProps: { @@ -72,7 +71,6 @@ export const WithInitialValue = { onChange: fn(), maxYear: new Date().getFullYear() - 11, yearsRange: 100, - locale: "en", }, }, } satisfies Story; @@ -84,7 +82,6 @@ export const WithError = { onChange: fn(), maxYear: new Date().getFullYear() - 11, yearsRange: 100, - locale: "en", }, }, } satisfies Story; @@ -96,7 +93,6 @@ export const WithCustomLocale = { onChange: fn(), maxYear: new Date().getFullYear() - 11, yearsRange: 100, - locale: "ru", }, }, } satisfies Story; @@ -108,7 +104,6 @@ export const WithCustomYearRange = { onChange: fn(), maxYear: 2000, yearsRange: 50, - locale: "en", }, }, } satisfies Story; @@ -120,7 +115,6 @@ export const WithoutBottomButton = { onChange: fn(), maxYear: new Date().getFullYear() - 11, yearsRange: 100, - locale: "en", }, bottomActionButtonProps: undefined, }, diff --git a/src/components/widgets/DateInput/DateInput.stories.tsx b/src/components/widgets/DateInput/DateInput.stories.tsx index bc84af3..e09aef9 100644 --- a/src/components/widgets/DateInput/DateInput.stories.tsx +++ b/src/components/widgets/DateInput/DateInput.stories.tsx @@ -27,11 +27,6 @@ const meta: Meta = { control: { type: "number" }, description: "Диапазон лет для выбора", }, - locale: { - control: { type: "select" }, - options: ["en", "ru", "de", "fr"], - description: "Локаль для отображения месяцев", - }, error: { control: { type: "text" }, description: "Сообщение об ошибке", @@ -74,10 +69,9 @@ export const WithError = { }, } satisfies Story; -export const RussianLocale = { +export const WithEmptyValue = { args: { value: null, - locale: "ru", }, } satisfies Story; diff --git a/src/components/widgets/DateInput/DateInput.tsx b/src/components/widgets/DateInput/DateInput.tsx index 4ee6fee..16366d9 100644 --- a/src/components/widgets/DateInput/DateInput.tsx +++ b/src/components/widgets/DateInput/DateInput.tsx @@ -17,7 +17,6 @@ export interface DateInputProps { maxYear?: number; yearsRange?: number; // onBlur?: () => void; - locale?: string; daySelectProps?: LocalSelectInputProps; monthSelectProps?: LocalSelectInputProps; yearSelectProps?: LocalSelectInputProps; @@ -30,7 +29,6 @@ export default function DateInput({ maxYear = new Date().getFullYear() - 11, yearsRange = 100, // onBlur, - locale = "en", daySelectProps, monthSelectProps, yearSelectProps, @@ -51,7 +49,6 @@ export default function DateInput({ onChange, maxYear, yearsRange, - locale, }); const inputs = { diff --git a/src/components/widgets/PrivacyTermsConsent/PrivacyTermsConsent.tsx b/src/components/widgets/PrivacyTermsConsent/PrivacyTermsConsent.tsx index 7e4b9cb..b8ccc7e 100644 --- a/src/components/widgets/PrivacyTermsConsent/PrivacyTermsConsent.tsx +++ b/src/components/widgets/PrivacyTermsConsent/PrivacyTermsConsent.tsx @@ -27,13 +27,13 @@ export default function PrivacyTermsConsent({ I agree to the{" "} {privacyPolicy && ( )} {", "} {termsOfUse && ( )}{" "} and to the use of cookies and tracking technologies, that require your diff --git a/src/hooks/useDateInput.ts b/src/hooks/useDateInput.ts index 119eff2..7816955 100644 --- a/src/hooks/useDateInput.ts +++ b/src/hooks/useDateInput.ts @@ -1,8 +1,12 @@ import { SelectInputProps } from "@/components/ui/SelectInput/SelectInput"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +// Валидация даты без зависимости от часовых зон +// Проверяет что дата существует (например, 31 февраля - невалидна) const isValidDate = (year: number, month: number, day: number) => { if (!year || !month || !day) return false; + // Используем Date в локальной зоне только для валидации + // Это безопасно, т.к. мы не сохраняем результат, только проверяем корректность const date = new Date(year, month - 1, day); return ( date.getFullYear() === year && @@ -25,13 +29,11 @@ const parseDateValue = ( return { year, month, day }; }; -// Упрощенное определение порядка полей даты на основе локали. -// В реальном приложении здесь лучше использовать данные из next-intl. -const getDateInputLocaleFormat = (locale: string): ("d" | "m" | "y")[] => { - const format = new Intl.DateTimeFormat(locale).format(new Date(2001, 1, 3)); // Используем 3/Feb/2001 - if (/^3.*2/.test(format)) return ["d", "m", "y"]; // 3/2/2001 -> d/m/y - if (/^2.*3/.test(format)) return ["m", "d", "y"]; // 2/3/2001 -> m/d/y - return ["y", "m", "d"]; // 2001/2/3 -> y/m/d +// Порядок полей даты: месяц-день-год (американский формат) +// Этот формат используется для всех локалей чтобы обеспечить единообразие +const getDateInputLocaleFormat = (): ("d" | "m" | "y")[] => { + // Всегда используем американский формат: месяц, день, год + return ["m", "d", "y"]; }; interface UseDateInputProps { @@ -39,7 +41,6 @@ interface UseDateInputProps { onChange?: (value: string | null) => void; maxYear?: number; yearsRange?: number; - locale?: string; } export const useDateInput = ({ @@ -47,7 +48,6 @@ export const useDateInput = ({ onChange, maxYear = new Date().getFullYear() - 11, yearsRange = 100, - locale = "en", }: UseDateInputProps) => { const [year, setYear] = useState(""); const [month, setMonth] = useState(""); @@ -125,8 +125,8 @@ export const useDateInput = ({ }, [year, month]); const localeFormat = useMemo( - () => getDateInputLocaleFormat(locale), - [locale] + () => getDateInputLocaleFormat(), + [] ); const handleYearChange: SelectInputProps["onValueChange"] = useCallback(