Browse Source
refactor: implement Flutter marriage data synchronization and improve keyboard visibility handling in information sheet
master
refactor: implement Flutter marriage data synchronization and improve keyboard visibility handling in information sheet
master
9 changed files with 527 additions and 184 deletions
-
22src/app/layout.tsx
-
2src/app/new-match/new-match-client.tsx
-
4src/components/Componentes/information-sheet.test.tsx
-
116src/components/Componentes/information-sheet.tsx
-
128src/hooks/marriage/use-profile-main.test.ts
-
101src/hooks/marriage/use-profile-main.ts
-
54src/lib/auth-bridge.test.ts
-
43src/lib/auth-bridge.ts
-
203src/lib/marriage-profile-contract.ts
@ -1 +1,204 @@ |
|||||
|
import type { |
||||
|
MarriageActiveCase, |
||||
|
MarriageActiveSubscription, |
||||
|
MarriageField, |
||||
|
MarriageMatchSummary, |
||||
|
MarriageProfileResponse, |
||||
|
MarriageRecommendedPlan, |
||||
|
} from "@/hooks/marriage/types"; |
||||
|
|
||||
export const REQUIRED_PROFILE_SECTION_COUNT = 10; |
export const REQUIRED_PROFILE_SECTION_COUNT = 10; |
||||
|
|
||||
|
/** |
||||
|
* 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 ?? ""; |
||||
|
return { |
||||
|
key: String(key), |
||||
|
label: String(label), |
||||
|
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: String( |
||||
|
rawActiveCase.case_id ?? |
||||
|
rawActiveCase.caseId ?? |
||||
|
rawActiveCase.id ?? |
||||
|
"", |
||||
|
), |
||||
|
status: |
||||
|
rawActiveCase.status ?? |
||||
|
rawActiveCase.case_status ?? |
||||
|
rawActiveCase.caseStatus ?? |
||||
|
"", |
||||
|
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" |
||||
|
? { |
||||
|
is_active: Boolean( |
||||
|
rawActiveSub.is_active ?? |
||||
|
rawActiveSub.isActive ?? |
||||
|
rawActiveSub.valid ?? |
||||
|
true, |
||||
|
), |
||||
|
is_valid: Boolean( |
||||
|
rawActiveSub.is_valid ?? |
||||
|
rawActiveSub.isValid ?? |
||||
|
rawActiveSub.valid ?? |
||||
|
true, |
||||
|
), |
||||
|
plan_title: |
||||
|
rawActiveSub.plan_title ?? rawActiveSub.planTitle ?? undefined, |
||||
|
expires_at: |
||||
|
rawActiveSub.expires_at ?? rawActiveSub.expiresAt ?? 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: Number(rawRecommendedPlan.price ?? 50), |
||||
|
discounted_price: |
||||
|
rawRecommendedPlan.discounted_price !== undefined |
||||
|
? Number(rawRecommendedPlan.discounted_price) |
||||
|
: rawRecommendedPlan.discountedPrice !== undefined |
||||
|
? Number(rawRecommendedPlan.discountedPrice) |
||||
|
: undefined, |
||||
|
discount_percent: |
||||
|
rawRecommendedPlan.discount_percent !== undefined |
||||
|
? Number(rawRecommendedPlan.discount_percent) |
||||
|
: rawRecommendedPlan.discountPercent !== undefined |
||||
|
? Number(rawRecommendedPlan.discountPercent) |
||||
|
: undefined, |
||||
|
} |
||||
|
: 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, |
||||
|
}; |
||||
|
} |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue