Polish mobile app for App Store review and expand CRUD.

Default to beenvoice.soconnor.dev with server settings hidden behind Advanced; add Entities tab with clients/businesses, invoice creation, UI fixes for dashboard layout, date fields, FAB position, and card-matched button radius.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-17 23:14:58 -04:00
parent 14c880123c
commit 6d2711e36e
41 changed files with 2410 additions and 181 deletions
+1 -1
View File
@@ -77,7 +77,7 @@ export function Button({
const styles = StyleSheet.create({
base: {
minHeight: 40,
borderRadius: radii.md,
borderRadius: radii.lg,
alignItems: "center",
justifyContent: "center",
paddingHorizontal: spacing.md,
+1
View File
@@ -29,6 +29,7 @@ const styles = StyleSheet.create({
paddingHorizontal: 20,
paddingVertical: spacing.md,
gap: spacing.sm,
alignItems: "stretch",
},
title: {
fontSize: 15,
+5 -1
View File
@@ -128,6 +128,8 @@ export function DateTimeField({
const styles = StyleSheet.create({
wrapper: {
gap: spacing.xs,
alignSelf: "stretch",
width: "100%",
},
label: {
fontSize: 13,
@@ -137,8 +139,10 @@ const styles = StyleSheet.create({
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
alignSelf: "stretch",
width: "100%",
borderWidth: 1,
borderRadius: radii.md,
borderRadius: radii.lg,
paddingHorizontal: spacing.md,
minHeight: 48,
paddingVertical: spacing.sm,
+106
View File
@@ -0,0 +1,106 @@
import { Ionicons } from "@expo/vector-icons";
import { Pressable, StyleSheet, Text, TextInput, View, type TextInputProps } from "react-native";
import { fonts, radii, spacing } from "@/constants/theme";
import { useAppTheme } from "@/contexts/ThemeContext";
type StepperInputProps = Omit<TextInputProps, "value" | "onChangeText"> & {
label: string;
value: string;
onChangeText: (value: string) => void;
step?: number;
min?: number;
};
export function StepperInput({
label,
value,
onChangeText,
step = 0.25,
min = 0,
...props
}: StepperInputProps) {
const { colors } = useAppTheme();
function adjust(delta: number) {
const current = Number.parseFloat(value) || 0;
const next = Math.max(min, Math.round((current + delta) * 100) / 100);
onChangeText(Number.isInteger(next) ? String(next) : String(next));
}
return (
<View style={styles.wrapper}>
<Text style={[styles.label, { color: colors.foreground }]}>{label}</Text>
<View
style={[
styles.field,
{ borderColor: colors.border, backgroundColor: colors.cardGlass },
]}
>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Decrease ${label}`}
hitSlop={6}
onPress={() => adjust(-step)}
style={({ pressed }) => [styles.stepButton, pressed && styles.stepPressed]}
>
<Ionicons name="remove" size={18} color={colors.foreground} />
</Pressable>
<TextInput
value={value}
onChangeText={onChangeText}
keyboardType="decimal-pad"
placeholderTextColor={colors.mutedForeground}
style={[styles.input, { color: colors.foreground }]}
{...props}
/>
<Pressable
accessibilityRole="button"
accessibilityLabel={`Increase ${label}`}
hitSlop={6}
onPress={() => adjust(step)}
style={({ pressed }) => [styles.stepButton, pressed && styles.stepPressed]}
>
<Ionicons name="add" size={18} color={colors.foreground} />
</Pressable>
</View>
</View>
);
}
const styles = StyleSheet.create({
wrapper: {
gap: spacing.sm,
},
label: {
fontSize: 14,
fontFamily: fonts.bodyMedium,
},
field: {
minHeight: 44,
borderWidth: 1,
borderRadius: radii.md,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: spacing.xs,
},
stepButton: {
width: 36,
height: 36,
alignItems: "center",
justifyContent: "center",
borderRadius: radii.sm,
},
stepPressed: {
opacity: 0.65,
},
input: {
flex: 1,
textAlign: "center",
fontSize: 14,
fontFamily: fonts.body,
paddingVertical: spacing.sm,
},
});