Add beenvoice mobile companion app with full dark mode support.

Expo app with dashboard, time clock, invoices, and settings — native tabs, glass UI, theme-aware components, and iOS Live Activities.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 22:36:37 -04:00
parent 8a7a8df477
commit 14c880123c
93 changed files with 8849 additions and 7849 deletions
+108
View File
@@ -0,0 +1,108 @@
import { Image } from "expo-image";
import { StyleSheet, Text, View, type ImageStyle, type ViewStyle } from "react-native";
import { useAppTheme } from "@/contexts/ThemeContext";
import { fonts } from "@/constants/theme";
type LogoSize = "xs" | "sm" | "md" | "lg";
const widths: Record<LogoSize, number> = {
xs: 104,
sm: 140,
md: 180,
lg: 220,
};
type LogoProps = {
size?: LogoSize;
style?: ViewStyle;
/** Force the light wordmark for dark backgrounds (e.g. status bar chrome). */
onDark?: boolean;
};
/** Full beenvoice wordmark from web `public/beenvoice-logo.png` */
export function Logo({ size = "md", style, onDark }: LogoProps) {
const { isDark } = useAppTheme();
const width = widths[size];
const height = width * (436 / 2970);
const useDarkAsset = onDark ?? isDark;
return (
<View style={[styles.row, styles.noShrink, style]}>
<Image
source={
useDarkAsset
? require("@/assets/images/beenvoice-logo-dark.png")
: require("@/assets/images/beenvoice-logo.png")
}
style={{ width, height }}
contentFit="contain"
/>
</View>
);
}
/** Square app icon mark — fixed aspect ratio so flex parents cannot squash it. */
export function LogoMark({
size = 32,
style,
}: {
size?: number;
style?: ImageStyle;
}) {
const flat = StyleSheet.flatten(style);
const width =
typeof flat?.width === "number"
? flat.width
: typeof flat?.height === "number"
? flat.height
: size;
const height = typeof flat?.height === "number" ? flat.height : width;
return (
<View style={[styles.markBox, { width, height }]}>
<Image
source={require("@/assets/images/icon.png")}
style={styles.markImage}
contentFit="contain"
accessibilityLabel="beenvoice"
/>
</View>
);
}
export function HeadingText({
children,
style,
}: {
children: React.ReactNode;
style?: object;
}) {
const { colors } = useAppTheme();
return (
<Text style={[styles.heading, { color: colors.foreground }, style]}>{children}</Text>
);
}
const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
},
noShrink: {
flexShrink: 0,
},
markBox: {
flexShrink: 0,
alignItems: "center",
justifyContent: "center",
},
markImage: {
width: "100%",
height: "100%",
},
heading: {
fontFamily: fonts.heading,
},
});