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,15 @@
|
||||
// App store and deep-link destinations used by the invite join page
|
||||
// (apps/web/app/join/[code]/page.tsx). These are the real, live listing URLs
|
||||
// already used by components/Hero.tsx, components/CTA.tsx,
|
||||
// components/Navbar.tsx, and components/Pricing.tsx.
|
||||
|
||||
export const APP_STORE_URL = 'https://apps.apple.com/gb/app/sauci-couple-games-romance/id6757159885'
|
||||
export const PLAY_STORE_URL = 'https://play.google.com/store/apps/details?id=com.sauci.app'
|
||||
|
||||
export function buildInviteSchemeUrl(code: string): string {
|
||||
return `app.sauci://join?code=${encodeURIComponent(code)}`
|
||||
}
|
||||
|
||||
export function buildInviteUniversalUrl(code: string): string {
|
||||
return `https://sauci.app/join/${encodeURIComponent(code)}`
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import { isValidInviteCode, normalizeInviteCode } from './inviteCode'
|
||||
|
||||
describe('isValidInviteCode', () => {
|
||||
it('accepts 8-character alphanumeric codes', () => {
|
||||
expect(isValidInviteCode('ABCD1234')).toBe(true)
|
||||
expect(isValidInviteCode('abcd1234')).toBe(true)
|
||||
})
|
||||
|
||||
it('rejects codes with the wrong length or characters', () => {
|
||||
expect(isValidInviteCode('ABC123')).toBe(false)
|
||||
expect(isValidInviteCode('ABCD12345')).toBe(false)
|
||||
expect(isValidInviteCode('ABCD-123')).toBe(false)
|
||||
expect(isValidInviteCode('')).toBe(false)
|
||||
expect(isValidInviteCode(null)).toBe(false)
|
||||
expect(isValidInviteCode(undefined)).toBe(false)
|
||||
})
|
||||
})
|
||||
|
||||
describe('normalizeInviteCode', () => {
|
||||
it('trims and uppercases', () => {
|
||||
expect(normalizeInviteCode(' abcd1234 ')).toBe('ABCD1234')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,15 @@
|
||||
// Shared invite code shape validation for the web join page. Mirrors the
|
||||
// mobile app's invite code shape (apps/mobile/src/lib/inviteLink.ts) and the
|
||||
// standalone API's invite_code column. This module only validates the shape
|
||||
// client-side; it never calls a private API.
|
||||
|
||||
const INVITE_CODE_PATTERN = /^[A-Za-z0-9]{8}$/
|
||||
|
||||
export function isValidInviteCode(code: string | null | undefined): boolean {
|
||||
if (!code) return false
|
||||
return INVITE_CODE_PATTERN.test(code.trim())
|
||||
}
|
||||
|
||||
export function normalizeInviteCode(code: string): string {
|
||||
return code.trim().toUpperCase()
|
||||
}
|
||||
Reference in new issue
Block a user