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.
 
 
 
 
 
 

257 lines
8.4 KiB

import type {
MarriageActiveCase,
MarriageActiveSubscription,
MarriageField,
MarriageMatchSummary,
MarriageProfileResponse,
MarriageRecommendedPlan,
} from "@/hooks/marriage/types";
export const REQUIRED_PROFILE_SECTION_COUNT = 9;
/**
* Normalizes a single MarriageField from various potential backend or Flutter bridge formats.
*/
export function normalizeMarriageField(raw: any): MarriageField | null {
if (!raw || typeof raw !== "object") return null;
const key = raw.key ?? raw.field_key ?? raw.fieldKey ?? raw.name ?? "";
const label = raw.label ?? raw.title ?? "";
const value = raw.value ?? raw.val ?? "";
const type = raw.type ?? raw.field_type ?? raw.fieldType ?? "text";
return {
key: String(key),
label: String(label),
type: String(type),
value: value ?? "",
option_id: raw.option_id ?? raw.optionId ?? null,
private: Boolean(raw.private ?? raw.is_private ?? false),
};
}
/**
* Normalizes a match_summary object supporting snake_case, camelCase, and candidate structures.
*/
export function normalizeMatchSummary(raw: any): MarriageMatchSummary | null {
if (!raw || typeof raw !== "object") return null;
const rawPublicInfo =
raw.public_info ??
raw.publicInfo ??
raw.fields ??
raw.public_fields ??
raw.publicFields ??
[];
const publicInfo: MarriageField[] = Array.isArray(rawPublicInfo)
? rawPublicInfo
.map(normalizeMarriageField)
.filter((f): f is MarriageField => f !== null)
: [];
const rawId = raw.id ?? raw.candidate_id ?? raw.candidateId ?? 0;
const rawGender = raw.gender ?? "female";
const rawPercent =
raw.overall_completion_percent ??
raw.overallCompletionPercent ??
raw.completion_percent ??
raw.completionPercent ??
0;
return {
id: Number(rawId) || 0,
gender: rawGender,
overall_completion_percent: Number(rawPercent) || 0,
public_info: publicInfo,
};
}
/**
* Normalizes a full MarriageProfileResponse from server, Flutter bridge, or localStorage.
* Guarantees consistent snake_case format and type safety.
*/
export function normalizeMarriageProfile(
raw: any,
): MarriageProfileResponse | null {
if (!raw || typeof raw !== "object") return null;
// Handle nested data wrappers e.g. { marriage: ... }, { profile: ... }, or { data: { marriage: ... } }
const data =
raw.marriage ??
raw.profile ??
raw.data?.marriage ??
raw.data?.profile ??
raw.data ??
raw;
if (!data || typeof data !== "object") return null;
const rawMatch =
data.match_summary ??
data.matchSummary ??
data.match ??
data.candidate ??
null;
const match_summary = normalizeMatchSummary(rawMatch);
const rawActiveCase = data.active_case ?? data.activeCase ?? null;
const active_case: MarriageActiveCase | null =
rawActiveCase && typeof rawActiveCase === "object"
? {
case_id: Number(
rawActiveCase.case_id ??
rawActiveCase.caseId ??
rawActiveCase.id ??
0,
),
status:
rawActiveCase.status ??
rawActiveCase.case_status ??
rawActiveCase.caseStatus ??
"introduced",
introduced_at:
rawActiveCase.introduced_at ??
rawActiveCase.introducedAt ??
new Date().toISOString(),
contact_shared_at:
rawActiveCase.contact_shared_at ??
rawActiveCase.contactSharedAt ??
null,
payment_done_at:
rawActiveCase.payment_done_at ??
rawActiveCase.paymentDoneAt ??
null,
minutes_since_contact_shared:
rawActiveCase.minutes_since_contact_shared ??
rawActiveCase.minutesSinceContactShared ??
null,
can_report_no_contact: Boolean(
rawActiveCase.can_report_no_contact ??
rawActiveCase.canReportNoContact ??
false,
),
can_report_outcome: Boolean(
rawActiveCase.can_report_outcome ??
rawActiveCase.canReportOutcome ??
false,
),
has_outcome_reported: Boolean(
rawActiveCase.has_outcome_reported ??
rawActiveCase.hasOutcomeReported ??
false,
),
outcome_reason:
rawActiveCase.outcome_reason ??
rawActiveCase.outcomeReason ??
null,
outcome_note:
rawActiveCase.outcome_note ??
rawActiveCase.outcomeNote ??
null,
outcome_reported_by:
rawActiveCase.outcome_reported_by ??
rawActiveCase.outcomeReportedBy ??
null,
my_action:
rawActiveCase.my_action ?? rawActiveCase.myAction ?? "pending",
other_action:
rawActiveCase.other_action ??
rawActiveCase.otherAction ??
undefined,
}
: null;
const rawActiveSub =
data.active_subscription ?? data.activeSubscription ?? null;
const active_subscription: MarriageActiveSubscription | null =
rawActiveSub && typeof rawActiveSub === "object"
? {
id: Number(rawActiveSub.id ?? 1),
plan: {
id: Number(rawActiveSub.plan?.id ?? 1),
title: String(rawActiveSub.plan?.title ?? "Subscription"),
price: String(rawActiveSub.plan?.price ?? "0"),
plan_type: String(rawActiveSub.plan?.plan_type ?? "standard"),
duration_days:
rawActiveSub.plan?.duration_days !== undefined
? Number(rawActiveSub.plan.duration_days)
: null,
usage_limit:
rawActiveSub.plan?.usage_limit !== undefined
? Number(rawActiveSub.plan.usage_limit)
: null,
},
start_date: String(rawActiveSub.start_date ?? new Date().toISOString()),
end_date: rawActiveSub.end_date ? String(rawActiveSub.end_date) : null,
total_usages: Number(rawActiveSub.total_usages ?? 0),
is_active: Boolean(
rawActiveSub.is_active ??
rawActiveSub.isActive ??
rawActiveSub.valid ??
true,
),
is_valid: Boolean(
rawActiveSub.is_valid ??
rawActiveSub.isValid ??
rawActiveSub.valid ??
true,
),
remaining_days:
rawActiveSub.remaining_days !== undefined
? Number(rawActiveSub.remaining_days)
: undefined,
}
: null;
const rawRecommendedPlan =
data.recommended_plan ?? data.recommendedPlan ?? null;
const recommended_plan: MarriageRecommendedPlan | null =
rawRecommendedPlan && typeof rawRecommendedPlan === "object"
? {
id: Number(rawRecommendedPlan.id ?? 1),
title: String(rawRecommendedPlan.title ?? ""),
price: String(rawRecommendedPlan.price ?? "50"),
}
: null;
return {
id: Number(data.id ?? data.profile_id ?? data.profileId ?? 0),
status: data.status ?? "pending_onboarding",
gender: data.gender ?? null,
age: data.age !== undefined && data.age !== null ? Number(data.age) : null,
is_registering_for_self:
data.is_registering_for_self !== undefined
? Boolean(data.is_registering_for_self)
: data.isRegisteringForSelf !== undefined
? Boolean(data.isRegisteringForSelf)
: null,
can_edit_profile:
data.can_edit_profile !== undefined
? Boolean(data.can_edit_profile)
: data.canEditProfile !== undefined
? Boolean(data.canEditProfile)
: true,
can_message_expert:
data.can_message_expert !== undefined
? Boolean(data.can_message_expert)
: data.canMessageExpert !== undefined
? Boolean(data.canMessageExpert)
: true,
active_subscription,
is_ready_for_match:
data.is_ready_for_match !== undefined
? Boolean(data.is_ready_for_match)
: data.isReadyForMatch !== undefined
? Boolean(data.isReadyForMatch)
: true,
active_case,
needs_subscription: Boolean(
data.needs_subscription ?? data.needsSubscription ?? false,
),
recommended_plan,
match_summary,
intro_video_url: data.intro_video_url ?? data.introVideoUrl ?? null,
intro_video_thumbnail_url:
data.intro_video_thumbnail_url ?? data.introVideoThumbnailUrl ?? null,
unseen_rejection: data.unseen_rejection ?? data.unseenRejection ?? null,
};
}