You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
345 lines
8.8 KiB
345 lines
8.8 KiB
import enQuestions from "@/data/questions/en.json";
|
|
import faQuestions from "@/data/questions/fa.json";
|
|
import type { MarriageGender } from "@/hooks/marriage/types";
|
|
import { defaultLocale, type Locale } from "@/translations/config";
|
|
import { getDictionary } from "@/translations/dictionaries";
|
|
|
|
export const bookingTerms = [
|
|
"You will be contacted by your consultant.",
|
|
"The call may start 10-15 minutes earlier or later than scheduled.",
|
|
"Make sure you are available and in a quiet place at least 10 minutes before the session.",
|
|
] as const;
|
|
|
|
export type QuestionCardIcon =
|
|
| "profile"
|
|
| "education"
|
|
| "details"
|
|
| "checklist"
|
|
| "contact"
|
|
| "family_marital";
|
|
|
|
type QuestionExtras = {
|
|
placeHolder: string;
|
|
range: [number, number];
|
|
options: string[];
|
|
noSearch?: boolean;
|
|
};
|
|
|
|
type QuestionAudienceRule = {
|
|
genders?: MarriageGender[];
|
|
maxAge?: number;
|
|
minAge?: number;
|
|
};
|
|
|
|
export type QuestionLogic = {
|
|
dependsOn: {
|
|
title: string;
|
|
values: string[];
|
|
};
|
|
};
|
|
|
|
export type QuestionField = {
|
|
title: string;
|
|
type: string;
|
|
required: boolean;
|
|
private?: boolean;
|
|
description: string;
|
|
tooltip: string;
|
|
extras: QuestionExtras;
|
|
audience?: QuestionAudienceRule;
|
|
requiredWhen?: QuestionAudienceRule;
|
|
logic?: QuestionLogic;
|
|
showGuardianNotice?: boolean;
|
|
originalSlug?: string;
|
|
originalIndex?: number;
|
|
englishTitle?: string;
|
|
};
|
|
|
|
export type QuestionListItem = {
|
|
slug: string;
|
|
title: string;
|
|
estimate: string;
|
|
progress: number;
|
|
icon: QuestionCardIcon;
|
|
required?: boolean;
|
|
note?: string;
|
|
showInfoBadge?: boolean;
|
|
summary: string;
|
|
checkpoints: readonly string[];
|
|
tooltip: string;
|
|
questions: readonly QuestionField[];
|
|
audience?: QuestionAudienceRule;
|
|
};
|
|
|
|
type RawQuestionListItem = {
|
|
title: string;
|
|
icon: string;
|
|
slug: string;
|
|
required?: boolean;
|
|
estimateTime: string;
|
|
tooltip: string;
|
|
progress: number;
|
|
description: string;
|
|
questions: QuestionField[];
|
|
audience?: QuestionAudienceRule;
|
|
};
|
|
|
|
const iconMap: Record<string, QuestionCardIcon> = {
|
|
"user-circle": "profile",
|
|
school: "education",
|
|
"heart-handshake": "details",
|
|
"file-text": "contact",
|
|
"layout-grid": "checklist",
|
|
};
|
|
|
|
const questionsByLocale: Record<string, RawQuestionListItem[]> = {
|
|
en: enQuestions as RawQuestionListItem[],
|
|
fa: faQuestions as RawQuestionListItem[],
|
|
};
|
|
|
|
function mapQuestionListItem(item: RawQuestionListItem): QuestionListItem {
|
|
return {
|
|
slug: item.slug,
|
|
title: item.title,
|
|
estimate: item.estimateTime,
|
|
progress: item.progress,
|
|
icon: iconMap[item.icon] ?? "details",
|
|
required: item.required,
|
|
showInfoBadge: Boolean(item.tooltip),
|
|
summary: item.description,
|
|
checkpoints: item.questions.map((question) => question.title),
|
|
tooltip: item.tooltip,
|
|
questions: item.questions,
|
|
};
|
|
}
|
|
|
|
export function getQuestionListItems(locale: Locale = defaultLocale) {
|
|
const rawItems =
|
|
questionsByLocale[locale] ??
|
|
questionsByLocale[defaultLocale] ??
|
|
questionsByLocale.en ??
|
|
[];
|
|
|
|
const enItems = questionsByLocale.en || [];
|
|
const mappedRawItems = rawItems.map((item) => {
|
|
const enItem = enItems.find((e) => e.slug === item.slug);
|
|
if (!enItem) return item;
|
|
return {
|
|
...item,
|
|
questions: item.questions.map((q, idx) => {
|
|
const enQ = enItem.questions[idx];
|
|
return {
|
|
...q,
|
|
englishTitle: enQ ? enQ.title : q.title,
|
|
};
|
|
}),
|
|
};
|
|
});
|
|
|
|
const items = mappedRawItems.map(mapQuestionListItem);
|
|
|
|
const fbIndex = items.findIndex((item) => item.slug === "family_background");
|
|
const mhIndex = items.findIndex(
|
|
(item) => item.slug === "marital_history_children",
|
|
);
|
|
|
|
if (fbIndex !== -1 && mhIndex !== -1) {
|
|
const fbItem = items[fbIndex];
|
|
const mhItem = items[mhIndex];
|
|
|
|
const dict = getDictionary(locale);
|
|
const mergedTitle =
|
|
(dict as any)["Family Background, Marital Status, and Children"] ||
|
|
"Family Background, Marital Status, and Children";
|
|
const mergedEstimate = (dict as any)["20 minutes"] || "20 minutes";
|
|
|
|
const fbQuestions = fbItem.questions.map((q, idx) => ({
|
|
...q,
|
|
originalSlug: "family_background",
|
|
originalIndex: idx,
|
|
}));
|
|
|
|
const mhQuestions = mhItem.questions.map((q, idx) => ({
|
|
...q,
|
|
originalSlug: "marital_history_children",
|
|
originalIndex: idx,
|
|
}));
|
|
|
|
const finalQuestions = [...fbQuestions, ...mhQuestions];
|
|
|
|
const mergedItem: QuestionListItem = {
|
|
slug: "family_marital_history",
|
|
title: mergedTitle,
|
|
estimate: mergedEstimate,
|
|
progress: 0,
|
|
icon: "family_marital",
|
|
required: fbItem.required || mhItem.required,
|
|
showInfoBadge: fbItem.showInfoBadge || mhItem.showInfoBadge,
|
|
summary: `${fbItem.summary}\n\n${mhItem.summary}`,
|
|
checkpoints: [...fbItem.checkpoints, ...mhItem.checkpoints],
|
|
tooltip: fbItem.tooltip || mhItem.tooltip,
|
|
questions: finalQuestions,
|
|
};
|
|
|
|
const newItems = [...items];
|
|
newItems[fbIndex] = mergedItem;
|
|
newItems.splice(mhIndex, 1);
|
|
|
|
// Reorder items: bring future_spouse_criteria and identity_verification above the tests
|
|
const personalityIndex = newItems.findIndex(
|
|
(item) => item.slug === "personality_test",
|
|
);
|
|
const spouseItem = newItems.find(
|
|
(item) => item.slug === "future_spouse_criteria",
|
|
);
|
|
const identityItem = newItems.find(
|
|
(item) => item.slug === "identity_verification",
|
|
);
|
|
|
|
if (personalityIndex !== -1 && (spouseItem || identityItem)) {
|
|
const filtered = newItems.filter(
|
|
(item) =>
|
|
item.slug !== "future_spouse_criteria" &&
|
|
item.slug !== "identity_verification",
|
|
);
|
|
const insertAt = filtered.findIndex(
|
|
(item) => item.slug === "personality_test",
|
|
);
|
|
if (insertAt !== -1) {
|
|
const result = [...filtered];
|
|
const itemsToInsert = [];
|
|
if (spouseItem) itemsToInsert.push(spouseItem);
|
|
if (identityItem) itemsToInsert.push(identityItem);
|
|
result.splice(insertAt, 0, ...itemsToInsert);
|
|
return result;
|
|
}
|
|
}
|
|
return newItems;
|
|
}
|
|
|
|
// Also handle reordering if fbIndex/mhIndex are not found (fallback)
|
|
const personalityIndex = items.findIndex(
|
|
(item) => item.slug === "personality_test",
|
|
);
|
|
const spouseItem = items.find(
|
|
(item) => item.slug === "future_spouse_criteria",
|
|
);
|
|
const identityItem = items.find(
|
|
(item) => item.slug === "identity_verification",
|
|
);
|
|
|
|
if (personalityIndex !== -1 && (spouseItem || identityItem)) {
|
|
const filtered = items.filter(
|
|
(item) =>
|
|
item.slug !== "future_spouse_criteria" &&
|
|
item.slug !== "identity_verification",
|
|
);
|
|
const insertAt = filtered.findIndex(
|
|
(item) => item.slug === "personality_test",
|
|
);
|
|
if (insertAt !== -1) {
|
|
const result = [...filtered];
|
|
const itemsToInsert = [];
|
|
if (spouseItem) itemsToInsert.push(spouseItem);
|
|
if (identityItem) itemsToInsert.push(identityItem);
|
|
result.splice(insertAt, 0, ...itemsToInsert);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
return items;
|
|
}
|
|
|
|
export function getQuestionListItemBySlug(
|
|
slug: string,
|
|
locale: Locale = defaultLocale,
|
|
) {
|
|
return getQuestionListItems(locale).find((item) => item.slug === slug);
|
|
}
|
|
|
|
function matchesAudienceRule(
|
|
rule: QuestionAudienceRule | undefined,
|
|
profile: {
|
|
age?: number | null;
|
|
gender?: MarriageGender | null;
|
|
},
|
|
) {
|
|
if (!rule) {
|
|
return true;
|
|
}
|
|
|
|
if (
|
|
rule.genders?.length &&
|
|
profile.gender &&
|
|
!rule.genders.includes(profile.gender)
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
typeof rule.minAge === "number" &&
|
|
profile.age != null &&
|
|
profile.age < rule.minAge
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
typeof rule.maxAge === "number" &&
|
|
profile.age != null &&
|
|
profile.age > rule.maxAge
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export function isQuestionListItemVisibleForProfile(
|
|
item: QuestionListItem,
|
|
profile: {
|
|
age?: number | null;
|
|
gender?: MarriageGender | null;
|
|
},
|
|
) {
|
|
return matchesAudienceRule(item.audience, profile);
|
|
}
|
|
|
|
export function isQuestionVisibleForProfile(
|
|
question: QuestionField,
|
|
profile: {
|
|
age?: number | null;
|
|
gender?: MarriageGender | null;
|
|
},
|
|
) {
|
|
return matchesAudienceRule(question.audience, profile);
|
|
}
|
|
|
|
export function isQuestionRequiredForProfile(
|
|
question: QuestionField,
|
|
profile: {
|
|
age?: number | null;
|
|
gender?: MarriageGender | null;
|
|
},
|
|
) {
|
|
return (
|
|
question.required ||
|
|
(Boolean(question.requiredWhen) &&
|
|
matchesAudienceRule(question.requiredWhen, profile))
|
|
);
|
|
}
|
|
|
|
export function getRequiredQuestionsCount(
|
|
slug: string,
|
|
profile: {
|
|
age?: number | null;
|
|
gender?: MarriageGender | null;
|
|
},
|
|
locale: Locale = defaultLocale,
|
|
) {
|
|
const item = getQuestionListItemBySlug(slug, locale);
|
|
if (!item) return 0;
|
|
|
|
return item.questions.filter((q) => isQuestionRequiredForProfile(q, profile))
|
|
.length;
|
|
}
|