diff --git a/README.md b/README.md index 05d1ab6..6e9d76b 100644 --- a/README.md +++ b/README.md @@ -84,12 +84,25 @@ bun run ios Full flow: [docs/ARCHITECTURE.md#multi-account-model](./docs/ARCHITECTURE.md#multi-account-model) -## Deep links +## Deep links & Shortcuts -| Scheme | Screen | -|--------|--------| +| URL | Action | +|-----|--------| | `beenvoice://reset-password?token=…` | Reset password | -| `beenvoice://timer` | Timer tab (from Live Activity) | +| `beenvoice://timer` | Open time clock | +| `beenvoice://shortcuts/clock-in` | Clock in (last client) | +| `beenvoice://shortcuts/clock-in?title=…` | Clock in with title | +| `beenvoice://shortcuts/clock-out` | Clock out running timer | + +**iOS Shortcuts / Siri** (dev build after `npx expo prebuild`): + +- **Clock In** — starts the timer with your last client +- **Clock Out** — stops the running timer +- **Open Time Clock** — opens the timer tab + +Try “Hey Siri, clock in with beenvoice” or add actions from the Shortcuts app under beenvoice. + +Rebuild iOS after pulling shortcut changes: `npx expo prebuild --platform ios && bun run ios` ## Project layout diff --git a/app.json b/app.json index c7601e4..ef74f72 100644 --- a/app.json +++ b/app.json @@ -12,7 +12,8 @@ "bundleIdentifier": "com.beenvoice.app", "icon": "./assets/beenvoice.icon", "infoPlist": { - "NSFaceIDUsageDescription": "Unlock beenvoice with Face ID when returning to the app." + "NSFaceIDUsageDescription": "Unlock beenvoice with Face ID when returning to the app.", + "NSUserNotificationsUsageDescription": "beenvoice sends reminders when it's time to send an invoice." } }, "android": { @@ -35,6 +36,14 @@ }, "plugins": [ "expo-dev-client", + [ + "expo-build-properties", + { + "ios": { + "buildReactNativeFromSource": true + } + } + ], "expo-router", "expo-secure-store", [ @@ -58,7 +67,16 @@ "faceIDPermission": "Unlock beenvoice with Face ID when returning to the app." } ], - "@react-native-community/datetimepicker" + [ + "expo-notifications", + { + "icon": "./assets/images/icon.png", + "color": "#18181B", + "sounds": [] + } + ], + "@react-native-community/datetimepicker", + "./plugins/withAppIntents.js" ], "experiments": { "typedRoutes": true diff --git a/app/(app)/_layout.tsx b/app/(app)/_layout.tsx index d676301..b9d1182 100644 --- a/app/(app)/_layout.tsx +++ b/app/(app)/_layout.tsx @@ -2,6 +2,8 @@ import { Platform } from "react-native"; import { NativeTabs } from "expo-router/unstable-native-tabs"; import { AppLockOverlay } from "@/components/AppLockOverlay"; +import { InvoiceReminderSync } from "@/components/InvoiceReminderSync"; +import { ShortcutHandler } from "@/components/ShortcutHandler"; import { useAppTheme } from "@/contexts/ThemeContext"; import { AppLockProvider } from "@/contexts/AppLockContext"; @@ -71,6 +73,8 @@ export default function AppLayout() { Settings + + ); diff --git a/app/(app)/index.tsx b/app/(app)/index.tsx index bba5587..bdb64b8 100644 --- a/app/(app)/index.tsx +++ b/app/(app)/index.tsx @@ -62,6 +62,7 @@ export default function DashboardScreen() { : "No change vs last month"; const maxRevenue = Math.max(...stats.revenueChartData.map((d) => d.revenue), 1); + const sendReminderDue = stats.sendReminderDue ?? []; return ( @@ -118,6 +119,26 @@ export default function DashboardScreen() { ) : null} + {sendReminderDue.length > 0 ? ( + + + + {sendReminderDue.length} draft{" "} + {sendReminderDue.length === 1 ? "invoice" : "invoices"} ready to send + + + {sendReminderDue + .slice(0, 2) + .map( + (inv) => + `${inv.invoicePrefix ?? "#"}${inv.invoiceNumber} (${inv.client?.name ?? "Client"})`, + ) + .join(" · ")} + + + + ) : null} +