Signed-off-by: Matt Bruce <mbrucedogs@gmail.com>
This commit is contained in:
parent
6b10f8b398
commit
cfa1e17b3e
40
APP_STORE_CONNECT.md
Normal file
40
APP_STORE_CONNECT.md
Normal file
@ -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
|
||||
@ -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)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,4 +31,4 @@ CLOUDKIT_CONTAINER_IDENTIFIER = iCloud.$(COMPANY_IDENTIFIER).$(APP_NAME)
|
||||
// APP CLIP CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
APPCLIP_DOMAIN = cards.example.com
|
||||
APPCLIP_DOMAIN = topdoglabs.com
|
||||
|
||||
@ -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.
|
||||
|
||||
214
Website_AppClip_Plan.md
Normal file
214
Website_AppClip_Plan.md
Normal file
@ -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_DOMAIN>/appclip/businesscard?id=<recordName>`
|
||||
- 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://<your-domain>/.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=<id>`
|
||||
- Rituals: `/appclip/rituals?id=<id>`
|
||||
- SelfieCam: `/appclip/selfie-cam?id=<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://<your-domain>/.well-known/apple-app-site-association`
|
||||
- Confirm file returns directly (no redirect/auth).
|
||||
2. Header check (terminal):
|
||||
|
||||
```bash
|
||||
curl -I https://<your-domain>/.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.
|
||||
Loading…
Reference in New Issue
Block a user