commit
b11ef9f2fd
@ -2360,7 +2360,7 @@
|
|||||||
},
|
},
|
||||||
"trustedByOver": {
|
"trustedByOver": {
|
||||||
"text": {
|
"text": {
|
||||||
"text": "Trusted by over **355,000 people."
|
"text": "Trusted by over **355,000** people."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"findingOneGuide": {
|
"findingOneGuide": {
|
||||||
@ -2373,7 +2373,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"text": {
|
"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": {
|
"blur": {
|
||||||
"text": {
|
"text": {
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
@ -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 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";
|
const pattern = screen.dateInput?.selectedDateFormat || "MMMM d, yyyy";
|
||||||
return formatDateByPattern(date, pattern);
|
return formatDateByPattern(yearNum, monthNum, dayNum, pattern);
|
||||||
}, [isoDate, screen.dateInput?.selectedDateFormat]);
|
}
|
||||||
|
|
||||||
|
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 && (
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -12,7 +12,7 @@ const buttonVariants = cva(
|
|||||||
"font-inter text-xl/[24px] font-bold text-primary-foreground",
|
"font-inter text-xl/[24px] font-bold text-primary-foreground",
|
||||||
"px-[27px] py-5",
|
"px-[27px] py-5",
|
||||||
"transition-all",
|
"transition-all",
|
||||||
"disabled:opacity-50",
|
"disabled:opacity-30",
|
||||||
"shadow-blue-glow"
|
"shadow-blue-glow"
|
||||||
),
|
),
|
||||||
{
|
{
|
||||||
|
|||||||
@ -16,7 +16,7 @@ const buttonVariants = cva(
|
|||||||
"pl-[26px] pr-[18px] py-[18px]",
|
"pl-[26px] pr-[18px] py-[18px]",
|
||||||
"transition-[background-color,border-color,color]",
|
"transition-[background-color,border-color,color]",
|
||||||
"duration-200",
|
"duration-200",
|
||||||
"disabled:opacity-50",
|
"disabled:opacity-30",
|
||||||
"border-2",
|
"border-2",
|
||||||
"[-webkit-tap-highlight-color:transparent]",
|
"[-webkit-tap-highlight-color:transparent]",
|
||||||
"[transform:translateZ(0)]"
|
"[transform:translateZ(0)]"
|
||||||
@ -78,7 +78,7 @@ function MainButton({
|
|||||||
<Label
|
<Label
|
||||||
data-disabled={disabled}
|
data-disabled={disabled}
|
||||||
className={cn(
|
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>}
|
{emoji && <span className="text-[40px]">{Array.from(emoji)[0]}</span>}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|
||||||
|
|||||||
@ -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 = {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -2368,7 +2368,7 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
|
|||||||
},
|
},
|
||||||
"trustedByOver": {
|
"trustedByOver": {
|
||||||
"text": {
|
"text": {
|
||||||
"text": "Trusted by over **355,000 people."
|
"text": "Trusted by over **355,000** people."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"findingOneGuide": {
|
"findingOneGuide": {
|
||||||
@ -2381,7 +2381,7 @@ export const BAKED_FUNNELS: Record<string, FunnelDefinition> = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"text": {
|
"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": {
|
"blur": {
|
||||||
"text": {
|
"text": {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user