mirror of
https://github.com/jjbrunton/Sauci.git
synced 2026-10-31 18:19:43 +00:00
feat(invite-funnel): add web join page and mobile deep-link hand-off
Adds a shareable https://sauci.app/join/{code} link as an alternative to manually typing an 8-character invite code: - apps/web: /join/[code] page that validates the code shape client-side, attempts the app.sauci:// scheme link, and falls back to a large code display with copy button plus App Store/Play Store links; PostHog events for page view, code copy, and store-button clicks. - apps/web: apple-app-site-association and assetlinks.json under /.well-known so iOS Universal Links and Android App Links can verify sauci.app for /join/*. The Android release cert SHA-256 fingerprint is a placeholder pending the real value. - apps/mobile: associated domains (iOS) and an intent filter (Android) for https://sauci.app/join/*, alongside the existing app.sauci scheme. - apps/mobile: parse incoming join links, pre-fill and highlight the code on the pairing screen, stash the code for signed-out/mid-onboarding users and apply it once they reach pairing, and offer a once-per-session clipboard hand-off that is never auto-applied. - apps/mobile: share message now leads with the join link, keeping the raw code visible as a fallback. - Analytics: invite_link_opened, pairing_code_prefilled (mobile); join_page_viewed, join_page_code_copied, join_page_store_button_clicked (web). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
5564f9bbab
commit
d48b44baa8
22 files changed
+950
-23
No files matched your search
@@ -263,6 +263,55 @@ DELETE delete-relationship
|
||||
Both users: Unpaired
|
||||
```
|
||||
|
||||
## Invite Funnel (Join Links)
|
||||
|
||||
The invite code can also be delivered as a tappable link instead of requiring
|
||||
the recipient to type it in manually.
|
||||
|
||||
**Link forms:**
|
||||
- Universal/App Link: `https://sauci.app/join/{code}` (served by `apps/web`,
|
||||
opens the app directly on a device that has it installed and verified).
|
||||
- Custom scheme: `app.sauci://join?code={code}` (used by the web join page's
|
||||
automatic hand-off attempt, and remains supported for any existing client
|
||||
behavior).
|
||||
|
||||
**Web fallback (`apps/web/app/join/[code]/page.tsx`):** validates the code
|
||||
shape client-side only (8-character alphanumeric; never calls a private API),
|
||||
attempts the custom scheme link, and otherwise shows the code with a copy
|
||||
button plus App Store / Play Store links. Copying only copies the raw code.
|
||||
|
||||
**Association files:** `apps/web/public/.well-known/apple-app-site-association`
|
||||
and `apps/web/public/.well-known/assetlinks.json` authorize `sauci.app` to open
|
||||
the app for `/join/*`. The Apple Team ID (`BXVPBATDZY`, from
|
||||
`apps/mobile/app.json` and `apps/mobile/eas.json`) and bundle/package id
|
||||
(`com.sauci.app`) are real values already used elsewhere in the repo. The
|
||||
Android release certificate SHA-256 fingerprint in `assetlinks.json` is a
|
||||
placeholder (`PLACEHOLDER_ANDROID_RELEASE_SHA256_CERT_FINGERPRINT`) and must
|
||||
be replaced with the real release signing certificate fingerprint before
|
||||
Android App Links will verify.
|
||||
|
||||
**Mobile link handling:** `apps/mobile/app.json` declares
|
||||
`associatedDomains: ["applinks:sauci.app"]` (iOS) and an intent filter for
|
||||
`https://sauci.app/join/*` (Android), in addition to the existing `app.sauci`
|
||||
scheme. `apps/mobile/app/_layout.tsx` parses incoming URLs
|
||||
(`src/lib/inviteLink.ts`) and either routes a signed-in, onboarded user
|
||||
straight to the pairing screen with the code pre-filled, or stashes the code
|
||||
(`src/lib/pendingInviteCode.ts`) for signed-out/mid-onboarding users. The
|
||||
stashed code is applied once the user reaches the pairing screen or the app's
|
||||
initial route (`apps/mobile/app/index.tsx`) after sign-in/onboarding
|
||||
completes.
|
||||
|
||||
**Deferred clipboard hand-off:** if an unpaired user opens the pairing screen
|
||||
with no code from a link or stash, the app checks the clipboard once per
|
||||
session (`src/lib/clipboardInviteOffer.ts`) for a string matching the invite
|
||||
code shape and offers to use it ("Use code XXXXXXXX from your clipboard?").
|
||||
The code is never applied automatically; the user must accept the offer.
|
||||
|
||||
**Analytics:** `invite_link_opened` (`source`: `universal_link` | `scheme` |
|
||||
`clipboard`), `pairing_code_prefilled` (mobile, Firebase Analytics), and
|
||||
`join_page_viewed` / `join_page_code_copied` / `join_page_store_button_clicked`
|
||||
(web, PostHog).
|
||||
|
||||
## Cascade Behavior
|
||||
|
||||
When a couple is deleted, foreign key cascades clean up:
|
||||
|
||||
Reference in new issue
Block a user