feat(mobile): improve pairing invite journey

This commit is contained in:
jjbrunton committed 2026-09-06 12:38:16 +01:00
1 parent 402c027b14
commit f9481054de
12 files changed
+335 -92

No files matched your search

+12
View File
@@ -281,6 +281,18 @@ export const Events = {
inviteLinkOpened: (source: "universal_link" | "scheme" | "clipboard") =>
logEvent("invite_link_opened", { source }),
pairingCodePrefilled: () => logEvent("pairing_code_prefilled"),
invitePromptViewed: (surface: "swipe_banner" | "pairing_waiting" | "swipe_no_partner", sealedBucket: string) => logEvent("invite_prompt_viewed", { surface, sealed_bucket: sealedBucket }),
inviteShareOpened: (surface: "pairing_waiting", channel: "system" | "sms" | "whatsapp" | "copy_link" | "copy_code") => logEvent("invite_share_opened", { surface, channel }),
pairingJoinAttempted: (codeSource: "link" | "stash" | "clipboard" | "manual") => logEvent("pairing_join_attempted", { code_source: codeSource }),
pairingJoinFailed: (reason: "invalid_shape" | "code_not_found" | "couple_full" | "already_paired" | "network" | "unknown", codeSource: "link" | "stash" | "clipboard" | "manual") => logEvent("pairing_join_failed", { reason, code_source: codeSource }),
pairingCompleted: (role: "inviter" | "joiner", codeSource: "link" | "stash" | "clipboard" | "manual", hadSealed: boolean) => logEvent("pairing_completed", { role, code_source: codeSource, had_sealed: hadSealed }),
pairingUnlockViewed: (role: "inviter" | "joiner", unlockedBucket: string) => logEvent("pairing_unlock_viewed", { role, unlocked_bucket: unlockedBucket }),
pairingUnlockCtaTapped: (role: "inviter" | "joiner", cta: "view_matches" | "start_session" | "dismiss") => logEvent("pairing_unlock_cta_tapped", { role, cta }),
pairingCancelPrompted: (surface: "waiting" | "conflict") => logEvent("pairing_cancel_prompted", { surface }),
clipboardInviteOfferShown: () => logEvent("clipboard_invite_offer_shown"),
clipboardInviteOfferResolved: (outcome: "accepted" | "dismissed") => logEvent("clipboard_invite_offer_resolved", { outcome }),
inviteConflictShown: (state: "already_paired" | "waiting_own_code") => logEvent("invite_conflict_shown", { state }),
inviteConflictResolved: (state: "already_paired" | "waiting_own_code", choice: "cancel_and_join" | "keep_own" | "acknowledged") => logEvent("invite_conflict_resolved", { state, choice }),
// Quiz events
quizStarted: () => logEvent("quiz_started"),
+3 -3
View File
@@ -5,10 +5,10 @@
* the recipient a concrete reason to join right away.
*/
export function buildInviteShareMessage(inviteCode: string, sealedCount: number): string {
const link = `https://sauci.app/join/${inviteCode} (or enter code ${inviteCode} in the app)`;
const link = `https://sauci.app/join/${inviteCode}`;
if (sealedCount > 0) {
const noun = sealedCount === 1 ? "question" : "questions";
return `Join me on Sauci! I have already answered ${sealedCount} ${noun} about us. Tap this link to unlock them: ${link}`;
return `Join me on Sauci! I've answered ${sealedCount} ${noun} about us. Join and we'll both see what we agree on. ${link} If the link does not open the app, enter code ${inviteCode} in Sauci.`;
}
return `Join me on Sauci! Tap this link to pair up instantly: ${link}`;
return `Join me on Sauci! ${link} If the link does not open the app, enter code ${inviteCode} in Sauci.`;
}
+15
View File
@@ -0,0 +1,15 @@
import AsyncStorage from "@react-native-async-storage/async-storage";
const keyFor = (userId: string, coupleId: string) => `paired_unlock_seen_${userId}_${coupleId}`;
export async function hasSeenPairedUnlock(userId: string, coupleId: string): Promise<boolean> {
return (await AsyncStorage.getItem(keyFor(userId, coupleId))) === "true";
}
export async function markPairedUnlockSeen(userId: string, coupleId: string): Promise<void> {
await AsyncStorage.setItem(keyFor(userId, coupleId), "true");
}
export async function clearPairedUnlockSeen(userId: string, coupleId: string | null): Promise<void> {
if (coupleId) await AsyncStorage.removeItem(keyFor(userId, coupleId));
}