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
+72 -20
View File
@@ -5,6 +5,7 @@ import {
View,
type TextInputProps,
} from "react-native";
import { Ionicons } from "@expo/vector-icons";
import { useAppTheme } from "@/contexts/ThemeContext";
import { fonts, radii, spacing } from "@/constants/theme";
@@ -13,31 +14,60 @@ type InputProps = TextInputProps & {
label: string;
error?: string;
required?: boolean;
leftIcon?: keyof typeof Ionicons.glyphMap;
labelAccessory?: React.ReactNode;
hint?: string;
};
export function Input({ label, error, required, style, ...props }: InputProps) {
export function Input({
label,
error,
required,
leftIcon,
labelAccessory,
hint,
style,
...props
}: InputProps) {
const { colors } = useAppTheme();
return (
<View style={styles.wrapper}>
<Text style={[styles.label, { color: colors.foreground }]}>
{label}
{required ? <Text style={{ color: colors.destructive }}> *</Text> : null}
</Text>
<TextInput
placeholderTextColor={colors.mutedForeground}
style={[
styles.input,
{
borderColor: colors.border,
color: colors.foreground,
backgroundColor: colors.cardGlass,
},
error && { borderColor: colors.destructive },
style,
]}
{...props}
/>
<View style={styles.labelRow}>
<Text style={[styles.label, { color: colors.foreground }]}>
{label}
{required ? <Text style={{ color: colors.destructive }}> *</Text> : null}
</Text>
{labelAccessory}
</View>
<View style={styles.field}>
{leftIcon ? (
<Ionicons
name={leftIcon}
size={16}
color={colors.mutedForeground}
style={styles.leftIcon}
/>
) : null}
<TextInput
placeholderTextColor={colors.mutedForeground}
style={[
styles.input,
leftIcon && styles.inputWithIcon,
{
borderColor: colors.border,
color: colors.foreground,
backgroundColor: colors.cardGlass,
},
error && { borderColor: colors.destructive },
style,
]}
{...props}
/>
</View>
{hint && !error ? (
<Text style={[styles.hint, { color: colors.mutedForeground }]}>{hint}</Text>
) : null}
{error ? <Text style={[styles.error, { color: colors.destructive }]}>{error}</Text> : null}
</View>
);
@@ -47,18 +77,40 @@ const styles = StyleSheet.create({
wrapper: {
gap: spacing.sm,
},
labelRow: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
gap: spacing.sm,
},
label: {
fontSize: 14,
fontFamily: fonts.bodyMedium,
},
field: {
position: "relative",
justifyContent: "center",
},
leftIcon: {
position: "absolute",
left: spacing.md,
zIndex: 1,
},
input: {
minHeight: 40,
minHeight: 44,
borderWidth: 1,
borderRadius: radii.md,
paddingHorizontal: spacing.md,
fontSize: 14,
fontFamily: fonts.body,
},
inputWithIcon: {
paddingLeft: spacing.md + 24,
},
hint: {
fontSize: 12,
fontFamily: fonts.body,
},
error: {
fontSize: 13,
fontFamily: fonts.body,