fix date select and link redirect

This commit is contained in:
dev.daminik00 2025-10-09 20:29:40 +02:00
parent fdb98c0322
commit e851961508
8 changed files with 36 additions and 46 deletions

View File

@ -45,7 +45,7 @@ export default function Legal({
link.className link.className
)} )}
> >
<Link href={link.href} target="_blank" rel="noopener noreferrer">{link.children}</Link> <Link href={link.href}>{link.children}</Link>
</Button> </Button>
))} ))}
</div> </div>

View File

@ -9,18 +9,19 @@ import { TemplateLayout } from "../layouts/TemplateLayout";
import { createTemplateLayoutProps } from "@/lib/funnel/templateHelpers"; 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 = [ const monthNames = [
"January", "February", "March", "April", "May", "June", "January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December" "July", "August", "September", "October", "November", "December"
]; ];
return pattern return pattern
.replace("MMMM", monthNames[date.getMonth()]) .replace("MMMM", monthNames[month - 1])
.replace("MMM", monthNames[date.getMonth()].substring(0, 3)) .replace("MMM", monthNames[month - 1].substring(0, 3))
.replace("yyyy", date.getFullYear().toString()) .replace("yyyy", year.toString())
.replace("dd", date.getDate().toString().padStart(2, '0')) .replace("dd", day.toString().padStart(2, '0'))
.replace("d", date.getDate().toString()); .replace("d", day.toString());
} }
interface DateTemplateProps { interface DateTemplateProps {
@ -83,12 +84,21 @@ export function DateTemplate({
// Форматированная дата для отображения // Форматированная дата для отображения
const formattedDate = useMemo(() => { const formattedDate = useMemo(() => {
if (!isoDate) return null; const { month, day, year } = selectedDate;
if (!month || !day || !year) return null;
const date = new Date(isoDate); const monthNum = parseInt(month);
const pattern = screen.dateInput?.selectedDateFormat || "MMMM d, yyyy"; const dayNum = parseInt(day);
return formatDateByPattern(date, pattern); const yearNum = parseInt(year);
}, [isoDate, screen.dateInput?.selectedDateFormat]);
// Валидация даты
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 ? ( const selectedDateDisplay = formattedDate && screen.dateInput?.showSelectedDate !== false ? (
@ -138,7 +148,6 @@ export function DateTemplate({
onChange={handleDateChange} onChange={handleDateChange}
maxYear={new Date().getFullYear() - 11} maxYear={new Date().getFullYear() - 11}
yearsRange={100} yearsRange={100}
locale="en"
/> />
{defaultTexts?.privacyBanner && ( {defaultTexts?.privacyBanner && (

View File

@ -689,8 +689,6 @@ export function TrialPaymentTemplate({
By clicking Continue, you agree to our{" "} By clicking Continue, you agree to our{" "}
<a <a
href="https://witlab.us/terms" href="https://witlab.us/terms"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-[#4B5563]" className="underline hover:text-[#4B5563]"
> >
Terms of Use & Service Terms of Use & Service
@ -698,8 +696,6 @@ export function TrialPaymentTemplate({
and{" "} and{" "}
<a <a
href="https://witlab.us/privacy" href="https://witlab.us/privacy"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-[#4B5563]" className="underline hover:text-[#4B5563]"
> >
Privacy Policy Privacy Policy

View File

@ -34,7 +34,6 @@ const meta: Meta<typeof QuestionDateAnswers> = {
onChange: fn(), onChange: fn(),
maxYear: new Date().getFullYear() - 11, maxYear: new Date().getFullYear() - 11,
yearsRange: 100, yearsRange: 100,
locale: "en",
}, },
bottomActionButtonProps: { bottomActionButtonProps: {
actionButtonProps: { actionButtonProps: {
@ -72,7 +71,6 @@ export const WithInitialValue = {
onChange: fn(), onChange: fn(),
maxYear: new Date().getFullYear() - 11, maxYear: new Date().getFullYear() - 11,
yearsRange: 100, yearsRange: 100,
locale: "en",
}, },
}, },
} satisfies Story; } satisfies Story;
@ -84,7 +82,6 @@ export const WithError = {
onChange: fn(), onChange: fn(),
maxYear: new Date().getFullYear() - 11, maxYear: new Date().getFullYear() - 11,
yearsRange: 100, yearsRange: 100,
locale: "en",
}, },
}, },
} satisfies Story; } satisfies Story;
@ -96,7 +93,6 @@ export const WithCustomLocale = {
onChange: fn(), onChange: fn(),
maxYear: new Date().getFullYear() - 11, maxYear: new Date().getFullYear() - 11,
yearsRange: 100, yearsRange: 100,
locale: "ru",
}, },
}, },
} satisfies Story; } satisfies Story;
@ -108,7 +104,6 @@ export const WithCustomYearRange = {
onChange: fn(), onChange: fn(),
maxYear: 2000, maxYear: 2000,
yearsRange: 50, yearsRange: 50,
locale: "en",
}, },
}, },
} satisfies Story; } satisfies Story;
@ -120,7 +115,6 @@ export const WithoutBottomButton = {
onChange: fn(), onChange: fn(),
maxYear: new Date().getFullYear() - 11, maxYear: new Date().getFullYear() - 11,
yearsRange: 100, yearsRange: 100,
locale: "en",
}, },
bottomActionButtonProps: undefined, bottomActionButtonProps: undefined,
}, },

View File

@ -27,11 +27,6 @@ const meta: Meta<typeof DateInput> = {
control: { type: "number" }, control: { type: "number" },
description: "Диапазон лет для выбора", description: "Диапазон лет для выбора",
}, },
locale: {
control: { type: "select" },
options: ["en", "ru", "de", "fr"],
description: "Локаль для отображения месяцев",
},
error: { error: {
control: { type: "text" }, control: { type: "text" },
description: "Сообщение об ошибке", description: "Сообщение об ошибке",
@ -74,10 +69,9 @@ export const WithError = {
}, },
} satisfies Story; } satisfies Story;
export const RussianLocale = { export const WithEmptyValue = {
args: { args: {
value: null, value: null,
locale: "ru",
}, },
} satisfies Story; } satisfies Story;

View File

@ -17,7 +17,6 @@ export interface DateInputProps {
maxYear?: number; maxYear?: number;
yearsRange?: number; yearsRange?: number;
// onBlur?: () => void; // onBlur?: () => void;
locale?: string;
daySelectProps?: LocalSelectInputProps; daySelectProps?: LocalSelectInputProps;
monthSelectProps?: LocalSelectInputProps; monthSelectProps?: LocalSelectInputProps;
yearSelectProps?: LocalSelectInputProps; yearSelectProps?: LocalSelectInputProps;
@ -30,7 +29,6 @@ export default function DateInput({
maxYear = new Date().getFullYear() - 11, maxYear = new Date().getFullYear() - 11,
yearsRange = 100, yearsRange = 100,
// onBlur, // onBlur,
locale = "en",
daySelectProps, daySelectProps,
monthSelectProps, monthSelectProps,
yearSelectProps, yearSelectProps,
@ -51,7 +49,6 @@ export default function DateInput({
onChange, onChange,
maxYear, maxYear,
yearsRange, yearsRange,
locale,
}); });
const inputs = { const inputs = {

View File

@ -27,13 +27,13 @@ export default function PrivacyTermsConsent({
I agree to the{" "} I agree to the{" "}
{privacyPolicy && ( {privacyPolicy && (
<Button variant="link" asChild {...privacyPolicy}> <Button variant="link" asChild {...privacyPolicy}>
<Link href={privacyPolicy.href} target="_blank" rel="noopener noreferrer">{privacyPolicy.children}</Link> <Link href={privacyPolicy.href}>{privacyPolicy.children}</Link>
</Button> </Button>
)} )}
{", "} {", "}
{termsOfUse && ( {termsOfUse && (
<Button variant="link" asChild {...termsOfUse}> <Button variant="link" asChild {...termsOfUse}>
<Link href={termsOfUse.href} target="_blank" rel="noopener noreferrer">{termsOfUse.children}</Link> <Link href={termsOfUse.href}>{termsOfUse.children}</Link>
</Button> </Button>
)}{" "} )}{" "}
and to the use of cookies and tracking technologies, that require your and to the use of cookies and tracking technologies, that require your

View File

@ -1,8 +1,12 @@
import { SelectInputProps } from "@/components/ui/SelectInput/SelectInput"; import { SelectInputProps } from "@/components/ui/SelectInput/SelectInput";
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
// Валидация даты без зависимости от часовых зон
// Проверяет что дата существует (например, 31 февраля - невалидна)
const isValidDate = (year: number, month: number, day: number) => { const isValidDate = (year: number, month: number, day: number) => {
if (!year || !month || !day) return false; if (!year || !month || !day) return false;
// Используем Date в локальной зоне только для валидации
// Это безопасно, т.к. мы не сохраняем результат, только проверяем корректность
const date = new Date(year, month - 1, day); const date = new Date(year, month - 1, day);
return ( return (
date.getFullYear() === year && date.getFullYear() === year &&
@ -25,13 +29,11 @@ const parseDateValue = (
return { year, month, day }; return { year, month, day };
}; };
// Упрощенное определение порядка полей даты на основе локали. // Порядок полей даты: месяц-день-год (американский формат)
// В реальном приложении здесь лучше использовать данные из next-intl. // Этот формат используется для всех локалей чтобы обеспечить единообразие
const getDateInputLocaleFormat = (locale: string): ("d" | "m" | "y")[] => { const getDateInputLocaleFormat = (): ("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 return ["m", "d", "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
}; };
interface UseDateInputProps { interface UseDateInputProps {
@ -39,7 +41,6 @@ interface UseDateInputProps {
onChange?: (value: string | null) => void; onChange?: (value: string | null) => void;
maxYear?: number; maxYear?: number;
yearsRange?: number; yearsRange?: number;
locale?: string;
} }
export const useDateInput = ({ export const useDateInput = ({
@ -47,7 +48,6 @@ export const useDateInput = ({
onChange, onChange,
maxYear = new Date().getFullYear() - 11, maxYear = new Date().getFullYear() - 11,
yearsRange = 100, yearsRange = 100,
locale = "en",
}: UseDateInputProps) => { }: UseDateInputProps) => {
const [year, setYear] = useState(""); const [year, setYear] = useState("");
const [month, setMonth] = useState(""); const [month, setMonth] = useState("");
@ -125,8 +125,8 @@ export const useDateInput = ({
}, [year, month]); }, [year, month]);
const localeFormat = useMemo( const localeFormat = useMemo(
() => getDateInputLocaleFormat(locale), () => getDateInputLocaleFormat(),
[locale] []
); );
const handleYearChange: SelectInputProps["onValueChange"] = useCallback( const handleYearChange: SelectInputProps["onValueChange"] = useCallback(