Browse Source

refactor: move marriage field formatting logic to library and add configurable SSR timeouts with debug logging

master
mortezaei 5 days ago
parent
commit
ea5d15c54c
  1. 93
      src/app/new-match/new-match-client.tsx
  2. 93
      src/app/new-match/profile/page.tsx
  3. 151
      src/lib/marriage-field-formatter.ts

93
src/app/new-match/new-match-client.tsx

@ -148,92 +148,13 @@ function calculateAgeFromDob(dobString: string): number | null {
return age > 0 && age < 120 ? age : null; return age > 0 && age < 120 ? age : null;
} }
function formatOptionValue(
value: MarriageFieldValue,
dictionary?: Record<string, string>,
): string | null {
if (Array.isArray(value)) {
const formattedItems = value
.map((item) => formatOptionValue(item as MarriageFieldValue, dictionary))
.filter(Boolean);
return formattedItems.length ? formattedItems.join(", ") : null;
}
const base = formatFieldValue(value);
if (!base) return null;
if (!dictionary) return base;
if (dictionary[base]) return dictionary[base];
// Try replacing underscores with spaces: "single;_never_married" -> "single; never married"
const withSpaces = base.replace(/_/g, " ").trim();
if (dictionary[withSpaces]) return dictionary[withSpaces];
// Try capitalized first letter: "Single; never married"
const capitalized = withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
if (dictionary[capitalized]) return dictionary[capitalized];
return withSpaces;
}
function formatFieldValue(value: MarriageFieldValue) {
if (value === null || value === "") {
return null;
}
if (isMarriagePhoneFieldValue(value)) {
return `+${value.countryCode}${value.phoneNumber}`;
}
if (typeof value === "boolean") {
return value ? "Yes" : "No";
}
return String(value);
}
function isMarriagePhoneFieldValue(
value: unknown,
): value is MarriagePhoneFieldValue {
if (!value || typeof value !== "object") {
return false;
}
const phoneValue = value as Partial<MarriagePhoneFieldValue>;
return (
typeof phoneValue.countryCode === "string" &&
typeof phoneValue.phoneNumber === "string"
);
}
function titleFromKey(key: string) {
const leafKey = key.includes(".") ? key.split(".").pop()! : key;
return leafKey
.replace(/^q\d+[_-]?/i, "")
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function formatFieldLabel(
field: MarriageField,
dictionary?: Record<string, string>,
): string {
const englishTitle = titleFromKey(field.key);
if (!dictionary) return field.label || englishTitle;
if (field.label && dictionary[field.label]) {
return dictionary[field.label];
}
if (dictionary[englishTitle]) {
return dictionary[englishTitle];
}
return field.label || englishTitle;
}
import {
formatFieldLabel,
formatFieldValue,
formatOptionValue,
isMarriagePhoneFieldValue,
titleFromKey,
} from "@/lib/marriage-field-formatter";
function toDisplayField( function toDisplayField(
field: MarriageField, field: MarriageField,

93
src/app/new-match/profile/page.tsx

@ -26,92 +26,13 @@ import { markMatchStarted } from "@/lib/match-start-grace";
import { localizePath } from "@/translations/config"; import { localizePath } from "@/translations/config";
import { useI18n } from "@/translations/provider"; import { useI18n } from "@/translations/provider";
function formatOptionValue(
value: MarriageFieldValue,
dictionary?: Record<string, string>,
): string | null {
if (Array.isArray(value)) {
const formattedItems = value
.map((item) => formatOptionValue(item as MarriageFieldValue, dictionary))
.filter(Boolean);
return formattedItems.length ? formattedItems.join(", ") : null;
}
const base = formatFieldValue(value);
if (!base) return null;
if (!dictionary) return base;
if (dictionary[base]) return dictionary[base];
// Try replacing underscores with spaces: "single;_never_married" -> "single; never married"
const withSpaces = base.replace(/_/g, " ").trim();
if (dictionary[withSpaces]) return dictionary[withSpaces];
// Try capitalized first letter: "Single; never married"
const capitalized = withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
if (dictionary[capitalized]) return dictionary[capitalized];
return withSpaces;
}
function formatFieldValue(value: MarriageFieldValue) {
if (value === null || value === "") {
return null;
}
if (isMarriagePhoneFieldValue(value)) {
return `+${value.countryCode}${value.phoneNumber}`;
}
if (typeof value === "boolean") {
return value ? "Yes" : "No";
}
return String(value);
}
function isMarriagePhoneFieldValue(
value: unknown,
): value is MarriagePhoneFieldValue {
if (!value || typeof value !== "object") {
return false;
}
const phoneValue = value as Partial<MarriagePhoneFieldValue>;
return (
typeof phoneValue.countryCode === "string" &&
typeof phoneValue.phoneNumber === "string"
);
}
function titleFromKey(key: string) {
const leafKey = key.includes(".") ? key.split(".").pop()! : key;
return leafKey
.replace(/^q\d+[_-]?/i, "")
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
function formatFieldLabel(
field: MarriageField,
dictionary?: Record<string, string>,
): string {
const englishTitle = titleFromKey(field.key);
if (!dictionary) return field.label || englishTitle;
if (field.label && dictionary[field.label]) {
return dictionary[field.label];
}
if (dictionary[englishTitle]) {
return dictionary[englishTitle];
}
return field.label || englishTitle;
}
import {
formatFieldLabel,
formatFieldValue,
formatOptionValue,
isMarriagePhoneFieldValue,
titleFromKey,
} from "@/lib/marriage-field-formatter";
function isImageField(field: MarriageField) { function isImageField(field: MarriageField) {
return /(avatar|image|photo|picture|portrait|upload)/i.test( return /(avatar|image|photo|picture|portrait|upload)/i.test(

151
src/lib/marriage-field-formatter.ts

@ -0,0 +1,151 @@
import type {
MarriageField,
MarriageFieldValue,
MarriagePhoneFieldValue,
} from "@/hooks/marriage/types";
import { dictionaries } from "@/translations/dictionaries";
const persianToEnglishMap: Record<string, string> = {};
for (const [enKey, faVal] of Object.entries(dictionaries.fa)) {
if (typeof faVal === "string") {
persianToEnglishMap[faVal.trim()] = enKey;
}
}
const normalizedEnglishKeys: Record<string, string> = {};
for (const enKey of Object.keys(dictionaries.en)) {
const norm = enKey.toLowerCase().replace(/[^a-z0-9]/g, "");
if (norm) {
normalizedEnglishKeys[norm] = enKey;
}
}
export function isMarriagePhoneFieldValue(
value: unknown,
): value is MarriagePhoneFieldValue {
if (!value || typeof value !== "object") {
return false;
}
const phoneValue = value as Partial<MarriagePhoneFieldValue>;
return (
typeof phoneValue.countryCode === "string" &&
typeof phoneValue.phoneNumber === "string"
);
}
export function formatFieldValue(value: MarriageFieldValue): string | null {
if (value === null || value === "") {
return null;
}
if (isMarriagePhoneFieldValue(value)) {
return `+${value.countryCode}${value.phoneNumber}`;
}
if (typeof value === "boolean") {
return value ? "Yes" : "No";
}
return String(value);
}
export function titleFromKey(key: string): string {
const leafKey = key.includes(".") ? key.split(".").pop()! : key;
return leafKey
.replace(/^q\d+[_-]?/i, "")
.replace(/[_-]+/g, " ")
.replace(/\s+/g, " ")
.trim()
.replace(/\b\w/g, (letter) => letter.toUpperCase());
}
export function formatFieldLabel(
field: MarriageField,
dictionary?: Record<string, string>,
): string {
const englishTitle = titleFromKey(field.key);
const rawLabel = (field.label || "").trim();
// 1. Direct dictionary match for field.label
if (rawLabel && dictionary?.[rawLabel]) {
return dictionary[rawLabel];
}
// 2. If rawLabel is in Persian, convert it to its canonical English key via reverse map
const enKeyFromFa = rawLabel ? persianToEnglishMap[rawLabel] : null;
if (enKeyFromFa) {
if (dictionary?.[enKeyFromFa]) {
return dictionary[enKeyFromFa];
}
return enKeyFromFa;
}
// 3. Direct match for English title derived from key
if (dictionary?.[englishTitle]) {
return dictionary[englishTitle];
}
// 4. Normalized match for key or label (e.g. ignoring punctuation/spaces like apostrophes and parentheses)
const normKey = (field.key || "").split(".").pop()?.toLowerCase().replace(/[^a-z0-9]/g, "") || "";
const matchedCanonicalKey = normalizedEnglishKeys[normKey];
if (matchedCanonicalKey) {
if (dictionary?.[matchedCanonicalKey]) {
return dictionary[matchedCanonicalKey];
}
return matchedCanonicalKey;
}
// 5. Fallback
return rawLabel || englishTitle;
}
export function formatOptionValue(
value: MarriageFieldValue,
dictionary?: Record<string, string>,
): string | null {
if (Array.isArray(value)) {
const formattedItems = value
.map((item) => formatOptionValue(item as MarriageFieldValue, dictionary))
.filter(Boolean);
return formattedItems.length ? formattedItems.join(", ") : null;
}
const base = formatFieldValue(value);
if (!base) return null;
const trimmed = base.trim();
// 1. Direct dictionary match
if (dictionary?.[trimmed]) return dictionary[trimmed];
// 2. Reverse map if base is in Persian
const enKeyFromFa = persianToEnglishMap[trimmed];
if (enKeyFromFa) {
if (dictionary?.[enKeyFromFa]) {
return dictionary[enKeyFromFa];
}
return enKeyFromFa;
}
// 3. Try replacing underscores with spaces: "single;_never_married" -> "single; never married"
const withSpaces = trimmed.replace(/_/g, " ").trim();
if (dictionary?.[withSpaces]) return dictionary[withSpaces];
// 4. Try capitalized first letter
const capitalized = withSpaces.charAt(0).toUpperCase() + withSpaces.slice(1);
if (dictionary?.[capitalized]) return dictionary[capitalized];
// 5. Normalized match for option value
const normVal = withSpaces.toLowerCase().replace(/[^a-z0-9]/g, "");
const matchedCanonicalKey = normalizedEnglishKeys[normVal];
if (matchedCanonicalKey) {
if (dictionary?.[matchedCanonicalKey]) {
return dictionary[matchedCanonicalKey];
}
return matchedCanonicalKey;
}
return withSpaces;
}
Loading…
Cancel
Save