diff --git a/APP_STORE_CONNECT.md b/APP_STORE_CONNECT.md new file mode 100644 index 0000000..f2d23eb --- /dev/null +++ b/APP_STORE_CONNECT.md @@ -0,0 +1,40 @@ +# BusinessCard App Store Connect Copy + +## Promotional Text (max 170) +Create polished digital business cards, share instantly with QR codes, and track every connection. Sync cards to Apple Watch and keep contacts organized with notes. + +Character count: 164 / 170 + +## Keywords (max 100) +business card,digital card,qr code,networking,contact manager,vcard,apple watch,lead tracking + +Character count: 93 / 100 + +## Description (max 4000) +BusinessCard helps you create modern digital business cards and share them in seconds. + +Build a card that fits your personal brand: +- Add profile, cover, and company logo images +- Choose from multiple header layouts +- Customize themes and card details +- Add unlimited contact fields, social profiles, and links + +Share your card anywhere: +- Show a QR code for instant scanning +- Share by text, email, WhatsApp, LinkedIn, and more +- Choose a default card to share quickly +- Enable offline sharing when needed + +Stay on top of your network: +- Track who received your card and when +- Save contacts manually or by scanning QR codes +- Add notes, tags, reminder dates, and where you met +- Search contacts fast across names, companies, tags, and notes + +Works across your Apple devices: +- iCloud sync keeps cards and contacts up to date on iPhone and iPad +- Apple Watch companion displays your default card QR code for quick sharing + +Designed for professionals who want faster follow-up and better relationship tracking after every introduction. + +Character count: 1035 / 4000 diff --git a/BusinessCard/Configuration/AppIdentifiers.swift b/BusinessCard/Configuration/AppIdentifiers.swift index 887a098..7012930 100644 --- a/BusinessCard/Configuration/AppIdentifiers.swift +++ b/BusinessCard/Configuration/AppIdentifiers.swift @@ -28,7 +28,7 @@ enum AppIdentifiers { /// App Clip domain for sharing URLs. static let appClipDomain: String = { Bundle.main.object(forInfoDictionaryKey: "AppClipDomain") as? String - ?? "cards.example.com" + ?? "topdoglabs.com" }() // MARK: - Derived Identifiers @@ -52,6 +52,6 @@ enum AppIdentifiers { /// Generates an App Clip invocation URL for a shared card record. static func appClipURL(recordName: String) -> URL? { - URL(string: "https://\(appClipDomain)/appclip?id=\(recordName)") + URL(string: "https://\(appClipDomain)/appclip/businesscard?id=\(recordName)") } } diff --git a/BusinessCard/Configuration/Base.xcconfig b/BusinessCard/Configuration/Base.xcconfig index c0dfc74..772ea2c 100644 --- a/BusinessCard/Configuration/Base.xcconfig +++ b/BusinessCard/Configuration/Base.xcconfig @@ -31,4 +31,4 @@ CLOUDKIT_CONTAINER_IDENTIFIER = iCloud.$(COMPANY_IDENTIFIER).$(APP_NAME) // APP CLIP CONFIGURATION // ============================================================================= -APPCLIP_DOMAIN = cards.example.com +APPCLIP_DOMAIN = topdoglabs.com diff --git a/BusinessCardClip/Configuration/ClipIdentifiers.swift b/BusinessCardClip/Configuration/ClipIdentifiers.swift index 89484f1..6296ce7 100644 --- a/BusinessCardClip/Configuration/ClipIdentifiers.swift +++ b/BusinessCardClip/Configuration/ClipIdentifiers.swift @@ -17,7 +17,7 @@ enum ClipIdentifiers { /// App Clip domain for URL handling. static let appClipDomain: String = { Bundle.main.object(forInfoDictionaryKey: "AppClipDomain") as? String - ?? "cards.example.com" + ?? "topdoglabs.com" }() /// Bundle identifier of the App Clip. diff --git a/Website_AppClip_Plan.md b/Website_AppClip_Plan.md new file mode 100644 index 0000000..6a476a9 --- /dev/null +++ b/Website_AppClip_Plan.md @@ -0,0 +1,214 @@ +# Website + App Clip Plan + +## Goal +Set up a production website/domain so the BusinessCard App Clip can be invoked reliably from QR codes and links. + +## Website Stack (Provided Context) +- Frontend: React 18 SPA with Vite and `BrowserRouter` +- Hosting: Vercel +- Backend endpoint: Vercel Serverless Function (`send-email.js`) +- Data-driven content from `site.json` and `apps.json` +- App slug on site: `businesscard` + +## Confirmed Website Reality (`/website`) +- Domain/site: TopDog Labs (`topdoglabs.com`) +- Routing: SPA routes in `website/src/App.jsx` (`/`, `/apps`, `/apps/:slug`, `/support`, etc.) +- Current Vercel behavior: global rewrite in `website/vercel.json`: + - `/(.*)` -> `/index.html` +- Impact: this current rewrite will also catch `/.well-known/apple-app-site-association` unless you add exclusions. + +## Current Project Context +- App currently builds BusinessCard App Clip URLs as: + - `https:///appclip/businesscard?id=` +- Domain is configured in `BusinessCard/Configuration/Base.xcconfig`: + - `APPCLIP_DOMAIN = topdoglabs.com` +- App and App Clip entitlements already reference: + - `appclips:$(APPCLIP_DOMAIN)` + +## What the Website Must Provide +1. A real HTTPS domain you control. +2. Apple App Site Association (AASA) file at: + - `https:///.well-known/apple-app-site-association` +3. Correct AASA delivery rules: + - No `.json` extension + - `Content-Type: application/json` + - Publicly accessible (no auth) + - No redirects +4. Optional fallback page/endpoint for non-iOS users. + +## No Required Language +- There is no required backend language for App Clips. +- You can use static hosting only, as long as AASA is served correctly. +- Use backend/worker only if you want advanced behavior (analytics, non-iOS vCard download, redirects, etc.). + +## Does Your Website `businesscard` Slug Matter? +- Not for App Clip verification itself. +- App Clip invocation for this iOS project uses `/appclip/businesscard?id=...`, not `/apps/:slug`. +- Keep `businesscard` slug for your public app page and marketing content. +- Ensure `/appclip/businesscard` and `/.well-known/apple-app-site-association` are not intercepted by SPA rewrites. + +## AASA File (Starter) +Create this file on your website: +- Path: `.well-known/apple-app-site-association` + +Use this template (replace values): + +```json +{ + "appclips": { + "apps": [ + "TEAM_ID.com.mbrucedogs.BusinessCard.Clip" + ] + } +} +``` + +Notes: +- `TEAM_ID` must match your Apple Developer Team ID. +- Bundle ID must match your App Clip target bundle ID exactly. + +## Vercel + React SPA Implementation +When you clone your website repo into this workspace, apply these steps. + +1. Add AASA as a static public file: + - `public/.well-known/apple-app-site-association` +2. Ensure your SPA fallback rewrite does not override AASA: + - Do not rewrite `/.well-known/*` to `index.html` +3. Ensure `/appclip/businesscard` is routed intentionally: + - Either serve a dedicated fallback page + - Or allow a specific serverless/edge handler for non-iOS behavior +4. Set explicit response header for AASA: + - `Content-Type: application/json` + +### Example `vercel.json` additions +Adjust to merge with your existing config: + +```json +{ + "cleanUrls": true, + "headers": [ + { + "source": "/.well-known/apple-app-site-association", + "headers": [ + { "key": "Content-Type", "value": "application/json" } + ] + } + ], + "rewrites": [ + { "source": "/.well-known/(.*)", "destination": "/.well-known/$1" }, + { "source": "/appclip/businesscard", "destination": "/appclip.html" }, + { "source": "/appclip", "destination": "/appclip.html" }, + { "source": "/api/(.*)", "destination": "/api/$1" }, + { "source": "/(.*)", "destination": "/index.html" } + ] +} +``` + +Notes: +- Keep `/.well-known/*` before catch-all SPA rewrite. +- Keep `/api/*` callable (your contact form endpoint). +- If you want `/appclip/businesscard` to show a fallback page, add `website/public/appclip.html`. + +## Minimal Files To Add in `website/public` +1. `website/public/.well-known/apple-app-site-association` +2. `website/public/appclip.html` (optional but recommended fallback) + +`appclip.html` can be minimal: +- Message for iPhone users to open via camera/App Clip card +- Link for non-iOS users (for example to app page `/apps/businesscard` or App Store URL) +- Optional JS to parse `id` query parameter and show it for support/debug + +## Multi-App App Clip Pattern (Recommended) +For multiple apps, use one shared domain and namespaced paths: + +- BusinessCard: `/appclip/businesscard?id=` +- Rituals: `/appclip/rituals?id=` +- SelfieCam: `/appclip/selfie-cam?id=` + +Keep one AASA file and include all App Clip bundle IDs: + +```json +{ + "appclips": { + "apps": [ + "TEAM_ID.com.mbrucedogs.BusinessCard.Clip", + "TEAM_ID.com.mbrucedogs.Rituals.Clip", + "TEAM_ID.com.mbrucedogs.SelfieCam.Clip" + ] + } +} +``` + +Then add one rewrite per App Clip path in `vercel.json`. + +## Domain + Hosting Checklist +1. Pick subdomain (recommended): `cards.yourdomain.com` +2. Configure DNS record to your host. +3. Ensure valid TLS certificate (HTTPS). +4. Add/upload AASA file at `/.well-known/apple-app-site-association`. +5. Verify no redirect occurs for that file. + +## App Project Changes Required +1. Update domain in `BusinessCard/Configuration/Base.xcconfig`: + - `APPCLIP_DOMAIN = cards.yourdomain.com` (recommended) + - or use an existing domain if preferred (for example a subpath on `topdoglabs.com`) +2. Confirm entitlements still resolve correctly: + - iOS app: `BusinessCard/BusinessCard.entitlements` + - App Clip: `BusinessCardClip/BusinessCardClip.entitlements` +3. Clean and rebuild in Xcode after config changes. + +Note: +- A dedicated subdomain (like `cards.topdoglabs.com`) is usually cleaner for App Clip + future deep link infrastructure. + +## App Store Connect Steps +1. Open your app in App Store Connect. +2. Create/configure App Clip Experience. +3. Add invocation URL using your real domain/path (for BusinessCard: + `/appclip/businesscard`). +4. Submit for review with App Clip metadata. + +## Verification Steps (Before Release) +1. Browser check: + - Open `https:///.well-known/apple-app-site-association` + - Confirm file returns directly (no redirect/auth). +2. Header check (terminal): + +```bash +curl -I https:///.well-known/apple-app-site-association +``` + +Confirm: +- `HTTP 200` +- `content-type: application/json` + +3. Device test: +- Install app + App Clip build on physical iPhone. +- Generate App Clip QR in app share screen. +- Scan QR and confirm App Clip opens. +- Confirm App Clip can fetch CloudKit card and save contact. + +4. Confirm SPA still works: +- `/` +- `/apps` +- `/apps/businesscard` +- `/support` +- `/.well-known/apple-app-site-association` +- `/appclip/businesscard?id=test` + +## Optional Fallback (Recommended) +For Android/desktop scans: +- Host a simple web page or download flow at `/appclip/businesscard`. +- Show contact preview or provide `.vcf` download if desired. + +## Suggested Rollout Order +1. Domain + AASA live +2. Update `APPCLIP_DOMAIN` +3. Rebuild + physical-device testing +4. App Store Connect App Clip Experience +5. Release + +## Risks / Gotchas +- Wrong Team ID or bundle ID in AASA breaks invocation. +- Redirecting `/.well-known/apple-app-site-association` breaks verification. +- Simulator is unreliable for full App Clip invocation testing. +- Keep CloudKit schema and public read permissions aligned with App Clip fetch flow.