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