commit
b11ef9f2fd
@ -2360,7 +2360,7 @@
|
||||
},
|
||||
"trustedByOver": {
|
||||
"text": {
|
||||
"text": "Trusted by over **355,000 people."
|
||||
"text": "Trusted by over **355,000** people."
|
||||
}
|
||||
},
|
||||
"findingOneGuide": {
|
||||
@ -2373,7 +2373,7 @@
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"text": "You’re special — you have imagination and energy that not everyone can understand. You deserve more than to be “half-understood” — you deserve to meet someone who sees the world as deeply and vividly as you do. This guide will help you recognize the signs of destiny, trust your path, and find the one whose heart is already connected to yours."
|
||||
"text": "You’re special — you have imagination and energy that not everyone can understand. You deserve more than to be “half-understood” — you deserve to meet someone who sees the world as deeply and vividly as you do. This guide will help you recognize the signs of destiny, trust your path, and find the one whose heart is already connected to yours.\nThey may already be closer than you think — drawn to your light, searching for the same rare connection. Stay open, follow your intuition, and notice the quiet moments that feel like fate. Every step you take brings you nearer to the soul that was always meant to walk beside you."
|
||||
},
|
||||
"blur": {
|
||||
"text": {
|
||||
|
||||
@ -45,7 +45,7 @@ export default function Legal({
|
||||
link.className
|
||||
)}
|
||||
>
|
||||
<Link href={link.href} target="_blank" rel="noopener noreferrer">{link.children}</Link>
|
||||
<Link href={link.href}>{link.children}</Link>
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@ -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 && (
|
||||
|
||||
@ -689,8 +689,6 @@ export function TrialPaymentTemplate({
|
||||
By clicking Continue, you agree to our{" "}
|
||||
<a
|
||||
href="https://witlab.us/terms"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-[#4B5563]"
|
||||
>
|
||||
Terms of Use & Service
|
||||
@ -698,8 +696,6 @@ export function TrialPaymentTemplate({
|
||||
and{" "}
|
||||
<a
|
||||
href="https://witlab.us/privacy"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="underline hover:text-[#4B5563]"
|
||||
>
|
||||
Privacy Policy
|
||||
|
||||
@ -34,7 +34,6 @@ const meta: Meta<typeof QuestionDateAnswers> = {
|
||||
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,
|
||||
},
|
||||
|
||||
@ -12,7 +12,7 @@ const buttonVariants = cva(
|
||||
"font-inter text-xl/[24px] font-bold text-primary-foreground",
|
||||
"px-[27px] py-5",
|
||||
"transition-all",
|
||||
"disabled:opacity-50",
|
||||
"disabled:opacity-30",
|
||||
"shadow-blue-glow"
|
||||
),
|
||||
{
|
||||
|
||||
@ -16,7 +16,7 @@ const buttonVariants = cva(
|
||||
"pl-[26px] pr-[18px] py-[18px]",
|
||||
"transition-[background-color,border-color,color]",
|
||||
"duration-200",
|
||||
"disabled:opacity-50",
|
||||
"disabled:opacity-30",
|
||||
"border-2",
|
||||
"[-webkit-tap-highlight-color:transparent]",
|
||||
"[transform:translateZ(0)]"
|
||||
@ -78,7 +78,7 @@ function MainButton({
|
||||
<Label
|
||||
data-disabled={disabled}
|
||||
className={cn(
|
||||
disabled && "pointer-events-none opacity-50 cursor-not-allowed"
|
||||
disabled && "pointer-events-none opacity-30 cursor-not-allowed"
|
||||
)}
|
||||
>
|
||||
{emoji && <span className="text-[40px]">{Array.from(emoji)[0]}</span>}
|
||||
|
||||
@ -27,11 +27,6 @@ const meta: Meta<typeof DateInput> = {
|
||||
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;
|
||||
|
||||
|
||||
@ -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 = {
|
||||
|
||||
@ -27,13 +27,13 @@ export default function PrivacyTermsConsent({
|
||||
I agree to the{" "}
|
||||
{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>
|
||||
)}
|
||||
{", "}
|
||||
{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>
|
||||
)}{" "}
|
||||
and to the use of cookies and tracking technologies, that require your
|
||||
|
||||
@ -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(
|
||||
|
||||
@ -2368,7 +2368,7 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
|
||||
},
|
||||
"trustedByOver": {
|
||||
"text": {
|
||||
"text": "Trusted by over **355,000 people."
|
||||
"text": "Trusted by over **355,000** people."
|
||||
}
|
||||
},
|
||||
"findingOneGuide": {
|
||||
@ -2381,7 +2381,7 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
|
||||
}
|
||||
},
|
||||
"text": {
|
||||
"text": "You’re special — you have imagination and energy that not everyone can understand. You deserve more than to be “half-understood” — you deserve to meet someone who sees the world as deeply and vividly as you do. This guide will help you recognize the signs of destiny, trust your path, and find the one whose heart is already connected to yours."
|
||||
"text": "You’re special — you have imagination and energy that not everyone can understand. You deserve more than to be “half-understood” — you deserve to meet someone who sees the world as deeply and vividly as you do. This guide will help you recognize the signs of destiny, trust your path, and find the one whose heart is already connected to yours.\nThey may already be closer than you think — drawn to your light, searching for the same rare connection. Stay open, follow your intuition, and notice the quiet moments that feel like fate. Every step you take brings you nearer to the soul that was always meant to walk beside you."
|
||||
},
|
||||
"blur": {
|
||||
"text": {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user