Files
beenvoice-app/widgets/TimeClockActivity.tsx
T
soconnorandCursor 6762a9bff3 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>
2026-06-26 03:40:48 -04:00

133 lines
3.7 KiB
TypeScript

import { HStack, Image, Spacer, Text } from "@expo/ui/swift-ui";
import {
font,
foregroundStyle,
lineLimit,
minimumScaleFactor,
monospacedDigit,
padding,
widgetAccentedRenderingMode,
} from "@expo/ui/swift-ui/modifiers";
import { createLiveActivity, type LiveActivityEnvironment } from "expo-widgets";
import type { TimeClockActivityProps } from "@/lib/time-clock-live-activity.types";
const TIMER_HORIZON_MS = 24 * 60 * 60 * 1000;
function liveTimer(
startedAtMs: number,
modifiers: ReturnType<typeof font>[],
) {
const lower = new Date(startedAtMs);
const upper = new Date(startedAtMs + TIMER_HORIZON_MS);
return (
<Text
timerInterval={{ lower, upper }}
countsDown={false}
modifiers={modifiers}
/>
);
}
function TimeClockActivity(props: TimeClockActivityProps, _environment: LiveActivityEnvironment) {
"widget";
const green = "green";
const title = props.description.trim() || "Clock In";
const clientLabel = props.clientName.trim() || title;
const subtitle = props.invoiceLabel.trim();
const startedAtMs = props.startedAtMs > 0 ? props.startedAtMs : Date.now();
const timerMods = [
font({ design: "monospaced", weight: "bold", size: 20 }),
monospacedDigit(),
foregroundStyle(green),
lineLimit(1),
minimumScaleFactor(0.85),
];
const compactTimerMods = [
font({ design: "monospaced", weight: "semibold", size: 11 }),
monospacedDigit(),
foregroundStyle(green),
lineLimit(1),
minimumScaleFactor(0.8),
];
const clientMods = [
font({ weight: "semibold", size: 13 }),
foregroundStyle({ type: "hierarchical", style: "primary" }),
lineLimit(1),
minimumScaleFactor(0.85),
];
const subtitleMods = [
font({ size: 11 }),
foregroundStyle({ type: "hierarchical", style: "secondary" }),
lineLimit(1),
minimumScaleFactor(0.85),
];
const bannerTimer = liveTimer(startedAtMs, timerMods);
const compactTimer = liveTimer(startedAtMs, compactTimerMods);
return {
banner: (
<HStack alignment="center" spacing={8} modifiers={[padding({ horizontal: 14, vertical: 12 })]}>
<Image
systemName="dollarsign.circle.fill"
color={green}
size={22}
modifiers={[widgetAccentedRenderingMode("fullColor")]}
/>
<Text modifiers={clientMods}>{clientLabel}</Text>
<Spacer minLength={12} />
{bannerTimer}
</HStack>
),
bannerSmall: (
<HStack alignment="center" spacing={8} modifiers={[padding({ horizontal: 12, vertical: 10 })]}>
<Image
systemName="dollarsign.circle.fill"
color={green}
size={18}
modifiers={[widgetAccentedRenderingMode("fullColor")]}
/>
<Text modifiers={clientMods}>{clientLabel}</Text>
<Spacer minLength={8} />
{compactTimer}
</HStack>
),
compactLeading: (
<Image
systemName="dollarsign.circle.fill"
color={green}
size={15}
modifiers={[widgetAccentedRenderingMode("fullColor")]}
/>
),
compactTrailing: compactTimer,
minimal: (
<Image
systemName="dollarsign.circle.fill"
color={green}
size={12}
modifiers={[widgetAccentedRenderingMode("fullColor")]}
/>
),
expandedLeading: (
<Image
systemName="dollarsign.circle.fill"
color={green}
size={20}
modifiers={[widgetAccentedRenderingMode("fullColor")]}
/>
),
expandedCenter: <Text modifiers={clientMods}>{clientLabel}</Text>,
expandedTrailing: bannerTimer,
expandedBottom: (
<Text modifiers={subtitleMods}>{subtitle || "beenvoice"}</Text>
),
};
}
export default createLiveActivity("TimeClockActivity", TimeClockActivity);