Move production to beenvoice.app with migrated accounts, refreshed auth and timer UX, and expanded invoice flows.

Official URL migration preserves sessions, shortcuts prefs, and last clock-in client; auth screens match web with legal links; time clock and invoice editor/send flows are updated for the new domain and UI patterns.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 03:40:48 -04:00
co-authored by Cursor
parent e17c4c6854
commit 6762a9bff3
60 changed files with 2544 additions and 1091 deletions
@@ -1,9 +1,8 @@
import SwiftUI
import UIKit
@available(iOS 18.0, *)
enum BeenVoiceIntentHelpers {
@MainActor
static func openDeepLink(_ url: URL) {
EnvironmentValues().openURL(url)
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
@@ -1,6 +1,5 @@
import AppIntents
@available(iOS 18.0, *)
struct BeenVoiceShortcuts: AppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
+1 -1
View File
@@ -1,10 +1,10 @@
import AppIntents
@available(iOS 18.0, *)
struct ClockInIntent: AppIntent {
static var title: LocalizedStringResource = "Clock In"
static var description = IntentDescription("Start the beenvoice time clock with your last client.")
static var openAppWhenRun: Bool = true
static var isDiscoverable: Bool = true
@Parameter(title: "Title")
var title: String?
+1 -1
View File
@@ -1,10 +1,10 @@
import AppIntents
@available(iOS 18.0, *)
struct ClockOutIntent: AppIntent {
static var title: LocalizedStringResource = "Clock Out"
static var description = IntentDescription("Stop the running beenvoice timer and save your time.")
static var openAppWhenRun: Bool = true
static var isDiscoverable: Bool = true
@MainActor
func perform() async throws -> some IntentResult {
+1 -1
View File
@@ -1,10 +1,10 @@
import AppIntents
@available(iOS 18.0, *)
struct OpenTimerIntent: AppIntent {
static var title: LocalizedStringResource = "Open Time Clock"
static var description = IntentDescription("Open the beenvoice time clock.")
static var openAppWhenRun: Bool = true
static var isDiscoverable: Bool = true
@MainActor
func perform() async throws -> some IntentResult {
+36 -11
View File
@@ -15,6 +15,10 @@ const SWIFT_FILES = [
"BeenVoiceShortcuts.swift",
];
const SHORTCUT_REGISTRATION = `Task {
await BeenVoiceShortcuts.updateAppShortcutParameters()
}`;
/** @type {import('@expo/config-plugins').ConfigPlugin} */
function withAppIntents(config) {
const appIntentsSource = path.join(
@@ -42,7 +46,6 @@ function withAppIntents(config) {
const appDelegatePath = path.join(targetDir, "AppDelegate.swift");
if (fs.existsSync(appDelegatePath)) {
let appDelegate = fs.readFileSync(appDelegatePath, "utf8");
const marker = "BeenVoiceShortcuts.updateAppShortcutParameters";
if (!appDelegate.includes("import AppIntents")) {
appDelegate = appDelegate.replace(
@@ -51,21 +54,29 @@ function withAppIntents(config) {
);
}
if (appDelegate.includes(marker)) {
if (!appDelegate.includes("BeenVoiceShortcuts.updateAppShortcutParameters")) {
appDelegate = appDelegate.replace(
/if #available\(iOS 16\.0, \*\)/g,
"if #available(iOS 18.0, *)",
"return super.application(application, didFinishLaunchingWithOptions: launchOptions)",
`${SHORTCUT_REGISTRATION}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)`,
);
} else {
appDelegate = appDelegate.replace(
"return super.application(application, didFinishLaunchingWithOptions: launchOptions)",
`if #available(iOS 18.0, *) {
Task {
await BeenVoiceShortcuts.updateAppShortcutParameters()
}
}
/if #available\(iOS 1[68]\.0, \*\) \{\s*Task \{\s*await BeenVoiceShortcuts\.updateAppShortcutParameters\(\)\s*\}\s*\}/g,
SHORTCUT_REGISTRATION,
);
}
return super.application(application, didFinishLaunchingWithOptions: launchOptions)`,
if (!appDelegate.includes("applicationDidBecomeActive")) {
appDelegate = appDelegate.replace(
" // Linking API",
` public override func applicationDidBecomeActive(_ application: UIApplication) {
${SHORTCUT_REGISTRATION}
super.applicationDidBecomeActive(application)
}
// Linking API`,
);
}
@@ -111,6 +122,20 @@ function withAppIntents(config) {
}
}
const configurations = project.pbxXCBuildConfigurationSection();
for (const key of Object.keys(configurations)) {
const buildConfig = configurations[key];
if (
typeof buildConfig !== "object" ||
!buildConfig.buildSettings ||
buildConfig.buildSettings.PRODUCT_BUNDLE_IDENTIFIER !== "com.beenvoice.app"
) {
continue;
}
buildConfig.buildSettings.EXTRACT_APP_INTENTS_METADATA = "YES";
}
return config;
});
}