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; |
|||
|
|||
/** |
|||
* 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