From ed14c57749e2b973c74ac0ec6a8565af3a2da2fe Mon Sep 17 00:00:00 2001 From: echo Date: Sun, 29 Mar 2026 20:07:33 +0200 Subject: [PATCH] normalize mobile auth and profile ui to theme components --- apps/mobile/src/app/(auth)/onboarding.tsx | 278 +++++++------- apps/mobile/src/app/(auth)/sign-in.tsx | 140 +++---- apps/mobile/src/app/(auth)/sign-up.tsx | 207 +++++------ apps/mobile/src/app/(tabs)/_layout.tsx | 2 +- apps/mobile/src/app/personal-details.tsx | 430 +++++++++------------- apps/mobile/src/components/Input.tsx | 56 ++- apps/mobile/src/components/Picker.tsx | 59 ++- 7 files changed, 557 insertions(+), 615 deletions(-) diff --git a/apps/mobile/src/app/(auth)/onboarding.tsx b/apps/mobile/src/app/(auth)/onboarding.tsx index 343a12c..1aa02fe 100644 --- a/apps/mobile/src/app/(auth)/onboarding.tsx +++ b/apps/mobile/src/app/(auth)/onboarding.tsx @@ -13,12 +13,17 @@ import { useUser, useAuth } from "@clerk/clerk-expo"; import { useRouter } from "expo-router"; import { fitnessProfileApi } from "@/api/fitnessProfile"; import { gymsApi, type Gym } from "@/api/gyms"; +import { useTheme } from "../../contexts/ThemeContext"; +import { Input } from "../../components/Input"; +import { MinimalButton } from "../../components/MinimalButton"; +import { MinimalCard } from "../../components/MinimalCard"; import log from "../../utils/logger"; export default function OnboardingScreen() { const { user } = useUser(); const { getToken } = useAuth(); const router = useRouter(); + const { colors, typography } = useTheme(); const [isSubmitting, setIsSubmitting] = useState(false); const [gyms, setGyms] = useState([]); const [gymsLoading, setGymsLoading] = useState(false); @@ -134,24 +139,53 @@ export default function OnboardingScreen() { const progress = calculateProgress(); return ( - - Set Up Your Fitness Profile - + + + Set Up Your Fitness Profile + + Help us personalize your fitness journey {/* Progress indicator */} - - + + - {progress}% Complete + + {progress}% Complete + - Height (cm) - setFitnessProfile({ ...fitnessProfile, height: value }) @@ -160,9 +194,8 @@ export default function OnboardingScreen() { placeholder="Enter height in cm" /> - Weight (kg) - setFitnessProfile({ ...fitnessProfile, weight: value }) @@ -171,9 +204,8 @@ export default function OnboardingScreen() { placeholder="Enter weight in kg" /> - Age - setFitnessProfile({ ...fitnessProfile, age: value }) @@ -182,9 +214,9 @@ export default function OnboardingScreen() { placeholder="Enter your age" /> - Fitness Goals - setFitnessProfile({ ...fitnessProfile, goals: value }) @@ -194,33 +226,48 @@ export default function OnboardingScreen() { placeholder="What are your fitness goals?" /> - Fitness Level + + Fitness Level + {["beginner", "intermediate", "advanced"].map((level) => ( handleLevelSelect(level)} > - {level.charAt(0).toUpperCase() + level.slice(1)} + {level} ))} - Medical Conditions - setFitnessProfile({ ...fitnessProfile, medicalConditions: value }) @@ -230,9 +277,9 @@ export default function OnboardingScreen() { placeholder="Any medical conditions we should know about?" /> - Dietary Restrictions - setFitnessProfile({ @@ -245,34 +292,47 @@ export default function OnboardingScreen() { placeholder="Any dietary restrictions?" /> - Preferred Workout Time + + Preferred Workout Time + {["morning", "afternoon", "evening"].map((time) => ( handleTimeSelect(time)} > - {time.charAt(0).toUpperCase() + time.slice(1)} + {time} ))} - Workouts per Week - setFitnessProfile({ ...fitnessProfile, workoutFrequency: value }) @@ -281,7 +341,11 @@ export default function OnboardingScreen() { placeholder="Number of workouts per week" /> - Select a Gym + + Select a Gym + {gymsLoading ? ( ) : ( @@ -293,15 +357,24 @@ export default function OnboardingScreen() { setSelectedGymId(null)} > Proceed without gym @@ -311,15 +384,26 @@ export default function OnboardingScreen() { setSelectedGymId(gym.id)} > {gym.name} @@ -330,17 +414,14 @@ export default function OnboardingScreen() { )} - - {isSubmitting ? ( - - ) : ( - Complete Setup - )} - + fullWidth + size="lg" + /> ); @@ -349,18 +430,13 @@ export default function OnboardingScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: "#f5f5f5", }, title: { - fontSize: 24, - fontWeight: "bold", textAlign: "center", marginTop: 40, marginBottom: 8, }, subtitle: { - fontSize: 16, - color: "#666", textAlign: "center", marginBottom: 32, }, @@ -368,91 +444,39 @@ const styles = StyleSheet.create({ padding: 20, }, label: { - fontSize: 14, - fontWeight: "600", - color: "#374151", - marginBottom: 4, - }, - input: { - borderWidth: 1, - borderColor: "#ddd", - borderRadius: 8, - padding: 12, - marginBottom: 16, - backgroundColor: "white", + marginBottom: 8, }, textArea: { - height: 80, + minHeight: 80, textAlignVertical: "top", + marginBottom: 16, }, buttonGroup: { flexDirection: "row", - justifyContent: "space-between", + flexWrap: "wrap", + gap: 8, marginBottom: 16, }, - levelButton: { - flex: 1, - backgroundColor: "#f3f4f6", + segmentButton: { + minWidth: 100, padding: 10, - borderRadius: 8, - marginHorizontal: 4, + borderRadius: 10, + borderWidth: 1, alignItems: "center", }, - timeButton: { - flex: 1, - backgroundColor: "#f3f4f6", - padding: 10, - borderRadius: 8, - marginHorizontal: 4, - alignItems: "center", - }, - selectedButton: { - backgroundColor: "#3b82f6", - }, - levelButtonText: { - color: "#374151", - fontSize: 14, - fontWeight: "500", - }, - timeButtonText: { - color: "#374151", - fontSize: 14, - fontWeight: "500", - }, - selectedButtonText: { - color: "white", - }, - submitButton: { - backgroundColor: "#3b82f6", - padding: 16, - borderRadius: 8, - alignItems: "center", - marginTop: 24, - }, - submitButtonText: { - color: "white", - fontSize: 16, - fontWeight: "600", - }, progressContainer: { paddingHorizontal: 20, marginBottom: 16, }, progressBarBackground: { height: 8, - backgroundColor: "#e5e7eb", borderRadius: 4, overflow: "hidden", marginBottom: 8, }, progressBarFill: { height: "100%", - backgroundColor: "#3b82f6", borderRadius: 4, }, - progressText: { - fontSize: 12, - color: "#6b7280", - textAlign: "center", - }, + progressText: { textAlign: "center" }, }); diff --git a/apps/mobile/src/app/(auth)/sign-in.tsx b/apps/mobile/src/app/(auth)/sign-in.tsx index 5f4b28b..3d6aa91 100644 --- a/apps/mobile/src/app/(auth)/sign-in.tsx +++ b/apps/mobile/src/app/(auth)/sign-in.tsx @@ -4,7 +4,6 @@ import { useRouter } from "expo-router"; import { View, Text, - TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, @@ -13,6 +12,9 @@ import { ScrollView, } from "react-native"; import { OAuthButtons } from "../../components/auth/OAuthButtons"; +import { Input } from "../../components/Input"; +import { MinimalButton } from "../../components/MinimalButton"; +import { useTheme } from "../../contexts/ThemeContext"; import { getErrorMessage, getClerkErrorCode } from "../../utils/error-helpers"; import log from "../../utils/logger"; @@ -25,6 +27,7 @@ export default function SignInScreen() { const [password, setPassword] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); + const { colors, typography } = useTheme(); // Redirect if already signed in useEffect(() => { @@ -77,8 +80,15 @@ export default function SignInScreen() { if (isSignedIn) { return ( - - Redirecting... + + + Redirecting... + ); } @@ -86,19 +96,33 @@ export default function SignInScreen() { return ( - Welcome Back - Sign in to continue to FitAI + + Welcome Back + + + Sign in to continue to FitAI + {error ? ( - {error} + + {error} + ) : null} @@ -112,13 +136,11 @@ export default function SignInScreen() { - Email - - Password - - - {loading ? ( - - ) : ( - Sign In - )} - + fullWidth + size="lg" + /> - Don't have an account? + + Don't have an account?{" "} + router.push("/(auth)/sign-up")} disabled={loading} > - Sign Up + + Sign Up + @@ -171,17 +194,11 @@ export default function SignInScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: "#f5f5f5", }, centerContent: { justifyContent: "center", alignItems: "center", }, - loadingText: { - marginTop: 12, - fontSize: 16, - color: "#666", - }, scrollContent: { flexGrow: 1, justifyContent: "center", @@ -193,27 +210,18 @@ const styles = StyleSheet.create({ paddingVertical: 40, }, title: { - fontSize: 32, - fontWeight: "bold", - color: "#1a1a1a", marginBottom: 8, }, subtitle: { - fontSize: 16, - color: "#666", marginBottom: 32, }, errorContainer: { - backgroundColor: "#fee", + backgroundColor: "rgba(255, 59, 59, 0.08)", padding: 12, - borderRadius: 8, + borderRadius: 12, marginBottom: 16, borderLeftWidth: 4, - borderLeftColor: "#f44", - }, - errorText: { - color: "#c00", - fontSize: 14, + borderLeftColor: "#FF3B3B", }, form: { marginBottom: 24, @@ -221,51 +229,11 @@ const styles = StyleSheet.create({ inputContainer: { marginBottom: 20, }, - label: { - fontSize: 14, - fontWeight: "600", - color: "#333", - marginBottom: 8, - }, - input: { - backgroundColor: "#fff", - borderWidth: 1, - borderColor: "#ddd", - borderRadius: 8, - paddingHorizontal: 16, - paddingVertical: 12, - fontSize: 16, - color: "#1a1a1a", - }, - button: { - backgroundColor: "#2563eb", - paddingVertical: 16, - borderRadius: 8, - alignItems: "center", - marginTop: 8, - }, - buttonDisabled: { - backgroundColor: "#93c5fd", - }, - buttonText: { - color: "#fff", - fontSize: 16, - fontWeight: "600", - }, footer: { flexDirection: "row", justifyContent: "center", alignItems: "center", }, - footerText: { - fontSize: 14, - color: "#666", - }, - linkText: { - fontSize: 14, - color: "#2563eb", - fontWeight: "600", - }, dividerContainer: { flexDirection: "row", alignItems: "center", @@ -274,11 +242,11 @@ const styles = StyleSheet.create({ dividerLine: { flex: 1, height: 1, - backgroundColor: "#ddd", + backgroundColor: "#E5E5EA", }, dividerText: { marginHorizontal: 10, - color: "#666", + color: "#8E8E93", fontSize: 14, }, }); diff --git a/apps/mobile/src/app/(auth)/sign-up.tsx b/apps/mobile/src/app/(auth)/sign-up.tsx index 3791e6d..d7e6921 100644 --- a/apps/mobile/src/app/(auth)/sign-up.tsx +++ b/apps/mobile/src/app/(auth)/sign-up.tsx @@ -4,7 +4,6 @@ import { useRouter } from "expo-router"; import { View, Text, - TextInput, TouchableOpacity, StyleSheet, ActivityIndicator, @@ -13,6 +12,9 @@ import { ScrollView, } from "react-native"; import { OAuthButtons } from "../../components/auth/OAuthButtons"; +import { Input } from "../../components/Input"; +import { MinimalButton } from "../../components/MinimalButton"; +import { useTheme } from "../../contexts/ThemeContext"; import { getErrorMessage, getClerkErrorCode } from "../../utils/error-helpers"; import log from "../../utils/logger"; @@ -29,6 +31,7 @@ export default function SignUpScreen() { const [code, setCode] = useState(""); const [loading, setLoading] = useState(false); const [error, setError] = useState(""); + const { colors, typography } = useTheme(); // Redirect if already signed in useEffect(() => { @@ -101,8 +104,15 @@ export default function SignUpScreen() { if (isSignedIn) { return ( - - Redirecting... + + + Redirecting... + ); } @@ -118,42 +128,53 @@ export default function SignUpScreen() { keyboardShouldPersistTaps="handled" > - Verify Email - + + Verify Email + + Enter the verification code sent to {emailAddress} {error ? ( - {error} + + {error} + ) : null} - Verification Code - - - {loading ? ( - - ) : ( - Verify Email - )} - + fullWidth + size="lg" + /> @@ -164,19 +185,33 @@ export default function SignUpScreen() { return ( - Create Account - Sign up to get started with FitAI + + Create Account + + + Sign up to get started with FitAI + {error ? ( - {error} + + {error} + ) : null} @@ -190,12 +225,10 @@ export default function SignUpScreen() { - First Name - - Last Name - - Email - - Password - - Must be at least 8 characters + + Must be at least 8 characters + - - {loading ? ( - - ) : ( - Sign Up - )} - + fullWidth + size="lg" + /> - Already have an account? + + Already have an account?{" "} + router.push("/(auth)/sign-in")} disabled={loading} > - Sign In + + Sign In + @@ -278,17 +316,11 @@ export default function SignUpScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: "#f5f5f5", }, centerContent: { justifyContent: "center", alignItems: "center", }, - loadingText: { - marginTop: 12, - fontSize: 16, - color: "#666", - }, scrollContent: { flexGrow: 1, justifyContent: "center", @@ -300,27 +332,18 @@ const styles = StyleSheet.create({ paddingVertical: 40, }, title: { - fontSize: 32, - fontWeight: "bold", - color: "#1a1a1a", marginBottom: 8, }, subtitle: { - fontSize: 16, - color: "#666", marginBottom: 32, }, errorContainer: { - backgroundColor: "#fee", + backgroundColor: "rgba(255, 59, 59, 0.08)", padding: 12, - borderRadius: 8, + borderRadius: 12, marginBottom: 16, borderLeftWidth: 4, - borderLeftColor: "#f44", - }, - errorText: { - color: "#c00", - fontSize: 14, + borderLeftColor: "#FF3B3B", }, form: { marginBottom: 24, @@ -328,56 +351,14 @@ const styles = StyleSheet.create({ inputContainer: { marginBottom: 20, }, - label: { - fontSize: 14, - fontWeight: "600", - color: "#333", - marginBottom: 8, - }, - input: { - backgroundColor: "#fff", - borderWidth: 1, - borderColor: "#ddd", - borderRadius: 8, - paddingHorizontal: 16, - paddingVertical: 12, - fontSize: 16, - color: "#1a1a1a", - }, hint: { - fontSize: 12, - color: "#999", marginTop: 4, }, - button: { - backgroundColor: "#2563eb", - paddingVertical: 16, - borderRadius: 8, - alignItems: "center", - marginTop: 8, - }, - buttonDisabled: { - backgroundColor: "#93c5fd", - }, - buttonText: { - color: "#fff", - fontSize: 16, - fontWeight: "600", - }, footer: { flexDirection: "row", justifyContent: "center", alignItems: "center", }, - footerText: { - fontSize: 14, - color: "#666", - }, - linkText: { - fontSize: 14, - color: "#2563eb", - fontWeight: "600", - }, dividerContainer: { flexDirection: "row", alignItems: "center", @@ -386,11 +367,11 @@ const styles = StyleSheet.create({ dividerLine: { flex: 1, height: 1, - backgroundColor: "#ddd", + backgroundColor: "#E5E5EA", }, dividerText: { marginHorizontal: 10, - color: "#666", + color: "#8E8E93", fontSize: 14, }, }); diff --git a/apps/mobile/src/app/(tabs)/_layout.tsx b/apps/mobile/src/app/(tabs)/_layout.tsx index 3199a1a..6ab4348 100644 --- a/apps/mobile/src/app/(tabs)/_layout.tsx +++ b/apps/mobile/src/app/(tabs)/_layout.tsx @@ -80,7 +80,7 @@ export default function TabLayout() { { - setLoading(true); - try { - // Update user profile via Clerk - await user?.update({ - firstName: formData.firstName, - lastName: formData.lastName, - }); + const updateField = (field: "firstName" | "lastName", value: string) => { + setFormData((prev) => ({ ...prev, [field]: value })); + }; - Alert.alert('Success', 'Personal details updated successfully', [ - { text: 'OK', onPress: () => router.back() }, - ]); - } catch (error) { - console.error('Error updating personal details:', error); - Alert.alert('Error', 'Failed to update personal details. Please try again.'); - } finally { - setLoading(false); - } - }; + const handleSave = async () => { + setLoading(true); + try { + await user?.update({ + firstName: formData.firstName, + lastName: formData.lastName, + }); - const updateField = (field: string, value: string) => { - setFormData(prev => ({ ...prev, [field]: value })); - }; + Alert.alert("Success", "Personal details updated successfully", [ + { text: "OK", onPress: () => router.back() }, + ]); + } catch { + Alert.alert( + "Error", + "Failed to update personal details. Please try again.", + ); + } finally { + setLoading(false); + } + }; - return ( - <> - - - {/* Header */} - - router.back()} - > - - - Personal Details - - + return ( + <> + + + + router.back()} + activeOpacity={0.8} + > + + + + Personal Details + + + - - {/* First Name */} - - First Name * - - - updateField('firstName', value)} - placeholder="Enter first name" - placeholderTextColor={theme.colors.gray400} - /> - - + + + updateField("firstName", value)} + placeholder="Enter first name" + /> - {/* Last Name */} - - Last Name * - - - updateField('lastName', value)} - placeholder="Enter last name" - placeholderTextColor={theme.colors.gray400} - /> - - + updateField("lastName", value)} + placeholder="Enter last name" + /> - {/* Email (Read-only) */} - - Email - - - - - - Email cannot be changed here - - - {/* Phone (Read-only for now) */} - - Phone Number - - - - - - Phone number cannot be changed here - - - - {/* Save Button */} - - - - - - {loading ? 'Saving...' : 'Save Changes'} - - - - + + + Email + + + {formData.email || "Not set"} + + + Read-only in app settings + - - ); + + + + Phone Number + + + {formData.phone || "Not set"} + + + Read-only in app settings + + + + + + + {loading ? ( + + + + ) : null} + + + + ); } const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: theme.colors.background, - }, - header: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingTop: Platform.OS === 'ios' ? 60 : 40, - paddingBottom: 20, - paddingHorizontal: 20, - }, - backButton: { - width: 40, - height: 40, - borderRadius: 20, - backgroundColor: 'rgba(255, 255, 255, 0.2)', - justifyContent: 'center', - alignItems: 'center', - }, - headerTitle: { - fontSize: theme.typography.fontSize['2xl'], - fontWeight: theme.typography.fontWeight.bold, - color: '#fff', - }, - content: { - flex: 1, - }, - scrollContent: { - padding: 20, - paddingBottom: 100, - }, - field: { - marginBottom: 24, - }, - label: { - fontSize: theme.typography.fontSize.sm, - fontWeight: theme.typography.fontWeight.semibold, - color: theme.colors.gray700, - marginBottom: 8, - }, - inputContainer: { - flexDirection: 'row', - alignItems: 'center', - backgroundColor: '#fff', - borderRadius: theme.borderRadius.lg, - borderWidth: 1, - borderColor: theme.colors.gray200, - paddingHorizontal: 16, - ...theme.shadows.subtle, - }, - inputIcon: { - marginRight: 12, - }, - input: { - flex: 1, - paddingVertical: 16, - fontSize: theme.typography.fontSize.base, - color: theme.colors.gray900, - }, - disabledInput: { - backgroundColor: theme.colors.gray50, - }, - disabledText: { - color: theme.colors.gray500, - }, - helperText: { - fontSize: theme.typography.fontSize.xs, - color: theme.colors.gray500, - marginTop: 6, - marginLeft: 4, - }, - footer: { - position: 'absolute', - bottom: 0, - left: 0, - right: 0, - padding: 20, - paddingBottom: Platform.OS === 'ios' ? 40 : 20, - backgroundColor: '#fff', - borderTopWidth: 1, - borderTopColor: theme.colors.gray100, - ...theme.shadows.medium, - }, - saveButton: { - borderRadius: theme.borderRadius.lg, - overflow: 'hidden', - }, - saveButtonGradient: { - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 16, - gap: 8, - }, - saveButtonDisabled: { - opacity: 0.6, - }, - saveButtonText: { - fontSize: theme.typography.fontSize.base, - fontWeight: theme.typography.fontWeight.bold, - color: '#fff', - }, + container: { + flex: 1, + }, + header: { + paddingTop: 56, + paddingBottom: 12, + paddingHorizontal: 20, + borderBottomWidth: 1, + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + }, + backButton: { + width: 36, + height: 36, + borderRadius: 10, + alignItems: "center", + justifyContent: "center", + }, + backButtonPlaceholder: { + width: 36, + height: 36, + }, + content: { + flex: 1, + }, + scrollContent: { + padding: 20, + paddingBottom: 80, + }, + card: { + borderRadius: 16, + }, + readOnlyField: { + marginTop: 8, + marginBottom: 12, + gap: 4, + }, + loadingRow: { + marginTop: 16, + alignItems: "center", + }, }); diff --git a/apps/mobile/src/components/Input.tsx b/apps/mobile/src/components/Input.tsx index a73e8ea..e8b8920 100644 --- a/apps/mobile/src/components/Input.tsx +++ b/apps/mobile/src/components/Input.tsx @@ -1,5 +1,12 @@ import React from "react"; -import { View, Text, TextInput, StyleSheet, TextInputProps } from "react-native"; +import { + View, + Text, + TextInput, + StyleSheet, + TextInputProps, +} from "react-native"; +import { useTheme } from "../contexts/ThemeContext"; interface InputProps extends TextInputProps { label: string; @@ -7,15 +14,39 @@ interface InputProps extends TextInputProps { } export function Input({ label, error, style, ...props }: InputProps) { + const { colors, typography } = useTheme(); + return ( - {label} + + {label} + - {error && {error}} + {error && ( + + {error} + + )} ); } @@ -25,27 +56,16 @@ const styles = StyleSheet.create({ marginBottom: 16, }, label: { - fontSize: 14, - fontWeight: "600", - color: "#374151", marginBottom: 8, }, input: { - backgroundColor: "white", borderWidth: 1, - borderColor: "#e5e7eb", - borderRadius: 8, + borderRadius: 12, paddingHorizontal: 16, - paddingVertical: 12, + paddingVertical: 14, fontSize: 16, - color: "#1f2937", - }, - inputError: { - borderColor: "#ef4444", }, errorText: { - fontSize: 12, - color: "#ef4444", marginTop: 4, }, }); diff --git a/apps/mobile/src/components/Picker.tsx b/apps/mobile/src/components/Picker.tsx index 504c58e..3c3d49c 100644 --- a/apps/mobile/src/components/Picker.tsx +++ b/apps/mobile/src/components/Picker.tsx @@ -1,6 +1,7 @@ import React from "react"; import { View, Text, StyleSheet } from "react-native"; import { Picker as RNPicker } from "@react-native-picker/picker"; +import { useTheme } from "../contexts/ThemeContext"; interface PickerProps { label: string; @@ -10,23 +11,57 @@ interface PickerProps { error?: string; } -export function Picker({ label, value, onValueChange, items, error }: PickerProps) { +export function Picker({ + label, + value, + onValueChange, + items, + error, +}: PickerProps) { + const { colors, typography } = useTheme(); + return ( - {label} - + + {label} + + {items.map((item) => ( - + ))} - {error && {error}} + {error && ( + + {error} + + )} ); } @@ -36,27 +71,17 @@ const styles = StyleSheet.create({ marginBottom: 16, }, label: { - fontSize: 14, - fontWeight: "600", - color: "#374151", marginBottom: 8, }, pickerWrapper: { - backgroundColor: "white", borderWidth: 1, - borderColor: "#e5e7eb", - borderRadius: 8, + borderRadius: 12, overflow: "hidden", }, - pickerError: { - borderColor: "#ef4444", - }, picker: { height: 50, }, errorText: { - fontSize: 12, - color: "#ef4444", marginTop: 4, }, });