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
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"applinks": {
|
||||
"apps": [],
|
||||
"details": [
|
||||
{
|
||||
"appID": "BXVPBATDZY.com.sauci.app",
|
||||
"paths": ["/join/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
[
|
||||
{
|
||||
"relation": ["delegate_permission/common.handle_all_urls"],
|
||||
"target": {
|
||||
"namespace": "android_app",
|
||||
"package_name": "com.sauci.app",
|
||||
"sha256_cert_fingerprints": [
|
||||
"PLACEHOLDER_ANDROID_RELEASE_SHA256_CERT_FINGERPRINT"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -0,0 +1,30 @@
|
||||
import { readFile } from 'node:fs/promises'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
const aasaPath = new URL('./apple-app-site-association', import.meta.url)
|
||||
const assetlinksPath = new URL('./assetlinks.json', import.meta.url)
|
||||
|
||||
describe('apple-app-site-association', () => {
|
||||
it('is valid JSON that covers /join/*', async () => {
|
||||
const raw = await readFile(aasaPath, 'utf8')
|
||||
const data = JSON.parse(raw)
|
||||
|
||||
expect(data.applinks.details).toHaveLength(1)
|
||||
expect(data.applinks.details[0].paths).toContain('/join/*')
|
||||
// com.sauci.app matches apps/mobile/app.json ios.bundleIdentifier.
|
||||
expect(data.applinks.details[0].appID).toMatch(/\.com\.sauci\.app$/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('assetlinks.json', () => {
|
||||
it('is valid JSON that targets the Android package', async () => {
|
||||
const raw = await readFile(assetlinksPath, 'utf8')
|
||||
const data = JSON.parse(raw)
|
||||
|
||||
expect(data[0].target.package_name).toBe('com.sauci.app')
|
||||
expect(data[0].target.namespace).toBe('android_app')
|
||||
// The real release cert fingerprint is not present in this repository;
|
||||
// this placeholder must be replaced before Android App Links will verify.
|
||||
expect(data[0].target.sha256_cert_fingerprints[0]).toContain('PLACEHOLDER')
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user