Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 | 2x 2x 4x 6x 3x 3x 3x 3x 3x 2x 2x 2x 2x 2x 2x 2x 2x 2x 3x 3x | import type { OnboardingData, RelationshipStatus } from "@/features/onboarding/useOnboarding";
import type { ProfileInput } from "@/lib/auth/callables";
/**
* Pure mappers from the server-owned Firestore documents to the local zustand
* stores. The auth bootstrap is the single writer of these fields, so all the
* shape/derivation logic lives here where it can be unit-tested without native SDKs.
*/
const DAY_MS = 86_400_000;
export type CoupleDoc = {
members: string[];
profiles?: Record<string, { displayName?: string | null }>;
togetherSince?: { toMillis: () => number } | null;
};
export type UserDoc = Partial<{
displayName: string;
gender: string | null;
age: number;
relationship: RelationshipStatus;
anniversary: string | null;
notificationsEnabled: boolean;
completed: boolean;
}>;
export type PairingSnapshot = {
isPaired: boolean;
partnerName: string | null;
togetherDays: number;
};
export const UNPAIRED: PairingSnapshot = { isPaired: false, partnerName: null, togetherDays: 0 };
/** Derive the pairing store from the couple doc (the day of pairing counts as day 1). */
export function pairingFromCouple(
couple: CoupleDoc | null,
uid: string,
nowMs: number,
): PairingSnapshot {
if (!couple) return UNPAIRED;
const partnerId = couple.members.find((m) => m !== uid) ?? null;
const partnerName = partnerId ? (couple.profiles?.[partnerId]?.displayName ?? null) : null;
const since = couple.togetherSince?.toMillis?.() ?? nowMs;
const togetherDays = Math.max(1, Math.floor((nowMs - since) / DAY_MS) + 1);
return { isPaired: true, partnerName, togetherDays };
}
/** Derive the onboarding store patch from the user doc (only fields that are present). */
export function onboardingFromUser(user: UserDoc | null): Partial<OnboardingData> {
if (!user) return {};
const out: Partial<OnboardingData> = {};
if (user.displayName !== undefined) out.displayName = user.displayName;
if (user.gender !== undefined) out.gender = user.gender;
if (user.age !== undefined) out.age = String(user.age);
if (user.relationship !== undefined) out.relationship = user.relationship;
if (user.anniversary !== undefined) out.anniversary = user.anniversary;
if (user.notificationsEnabled !== undefined) out.notificationsEnabled = user.notificationsEnabled;
Eif (user.completed !== undefined) out.completed = user.completed;
return out;
}
/** Map the local onboarding answers to the `updateProfile` payload (age → number). */
export function profileInputFromOnboarding(data: OnboardingData): ProfileInput {
const age = Number(data.age);
return {
displayName: data.displayName || undefined,
gender: data.gender,
age: Number.isFinite(age) && data.age !== "" ? age : undefined,
relationship: data.relationship ?? undefined,
anniversary: data.anniversary,
notificationsEnabled: data.notificationsEnabled,
};
}
|