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

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;
}