goal creation style fix

This commit is contained in:
echo 2026-03-11 08:55:05 +01:00
parent b1439f059a
commit a5f761062e
2 changed files with 499 additions and 399 deletions

Binary file not shown.

View File

@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState } from "react";
import { import {
View, View,
Text, Text,
@ -9,10 +9,12 @@ import {
ScrollView, ScrollView,
Platform, Platform,
Alert, Alert,
} from 'react-native'; } from "react-native";
import { Ionicons } from '@expo/vector-icons'; import { Ionicons } from "@expo/vector-icons";
import DateTimePicker from '@react-native-community/datetimepicker'; import DateTimePicker from "@react-native-community/datetimepicker";
import type { CreateGoalData } from '../services/fitnessGoals'; import type { CreateGoalData } from "../services/fitnessGoals";
import { useTheme } from "../contexts/ThemeContext";
import { MinimalButton } from "./MinimalButton";
interface GoalCreationModalProps { interface GoalCreationModalProps {
visible: boolean; visible: boolean;
@ -21,46 +23,52 @@ interface GoalCreationModalProps {
} }
const GOAL_TYPES = [ const GOAL_TYPES = [
{ value: 'weight_target', label: 'Weight Target' }, { value: "weight_target", label: "Weight Target" },
{ value: 'strength_milestone', label: 'Strength Milestone' }, { value: "strength_milestone", label: "Strength Milestone" },
{ value: 'endurance_target', label: 'Endurance Target' }, { value: "endurance_target", label: "Endurance Target" },
{ value: 'flexibility_goal', label: 'Flexibility Goal' }, { value: "flexibility_goal", label: "Flexibility Goal" },
{ value: 'habit_building', label: 'Habit Building' }, { value: "habit_building", label: "Habit Building" },
{ value: 'custom', label: 'Custom Goal' }, { value: "custom", label: "Custom Goal" },
] as const; ] as const;
const PRIORITIES = [ const PRIORITIES = [
{ value: 'low', label: 'Low', color: '#10b981' }, { value: "low", label: "Low" },
{ value: 'medium', label: 'Medium', color: '#f59e0b' }, { value: "medium", label: "Medium" },
{ value: 'high', label: 'High', color: '#ef4444' }, { value: "high", label: "High" },
] as const; ] as const;
export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationModalProps) { export function GoalCreationModal({
const [goalType, setGoalType] = useState<CreateGoalData['goalType']>('weight_target'); visible,
const [title, setTitle] = useState(''); onClose,
const [description, setDescription] = useState(''); onSubmit,
const [targetValue, setTargetValue] = useState(''); }: GoalCreationModalProps) {
const [currentValue, setCurrentValue] = useState(''); const { colors, typography } = useTheme();
const [unit, setUnit] = useState(''); const [goalType, setGoalType] =
const [priority, setPriority] = useState<'low' | 'medium' | 'high'>('medium'); useState<CreateGoalData["goalType"]>("weight_target");
const [title, setTitle] = useState("");
const [description, setDescription] = useState("");
const [targetValue, setTargetValue] = useState("");
const [currentValue, setCurrentValue] = useState("");
const [unit, setUnit] = useState("");
const [priority, setPriority] = useState<"low" | "medium" | "high">("medium");
const [targetDate, setTargetDate] = useState<Date | undefined>(); const [targetDate, setTargetDate] = useState<Date | undefined>();
const [showDatePicker, setShowDatePicker] = useState(false); const [showDatePicker, setShowDatePicker] = useState(false);
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
const resetForm = () => { const resetForm = () => {
setGoalType('weight_target'); setGoalType("weight_target");
setTitle(''); setTitle("");
setDescription(''); setDescription("");
setTargetValue(''); setTargetValue("");
setCurrentValue(''); setCurrentValue("");
setUnit(''); setUnit("");
setPriority('medium'); setPriority("medium");
setTargetDate(undefined); setTargetDate(undefined);
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
if (!title.trim()) { if (!title.trim()) {
Alert.alert('Error', 'Please enter a goal title'); Alert.alert("Error", "Please enter a goal title");
return; return;
} }
@ -81,8 +89,8 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
resetForm(); resetForm();
onClose(); onClose();
} catch (error) { } catch (error) {
console.error('Error creating goal:', error); console.error("Error creating goal:", error);
Alert.alert('Error', 'Failed to create goal. Please try again.'); Alert.alert("Error", "Failed to create goal. Please try again.");
} finally { } finally {
setSubmitting(false); setSubmitting(false);
} }
@ -93,6 +101,17 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
onClose(); onClose();
}; };
const getPriorityColor = (p: "low" | "medium" | "high") => {
switch (p) {
case "high":
return colors.danger;
case "medium":
return colors.warning;
case "low":
return colors.success;
}
};
return ( return (
<Modal <Modal
visible={visible} visible={visible}
@ -100,32 +119,61 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
presentationStyle="pageSheet" presentationStyle="pageSheet"
onRequestClose={handleClose} onRequestClose={handleClose}
> >
<View style={styles.container}> <View style={[styles.container, { backgroundColor: colors.background }]}>
<View style={styles.header}> <View
<Text style={styles.headerTitle}>Create Fitness Goal</Text> style={[
styles.header,
{
backgroundColor: colors.surface,
borderBottomColor: colors.border,
},
]}
>
<Text style={[typography.h2, { color: colors.textPrimary }]}>
Create Fitness Goal
</Text>
<TouchableOpacity onPress={handleClose} style={styles.closeButton}> <TouchableOpacity onPress={handleClose} style={styles.closeButton}>
<Ionicons name="close" size={28} color="#111827" /> <Ionicons name="close" size={28} color={colors.textPrimary} />
</TouchableOpacity> </TouchableOpacity>
</View> </View>
<ScrollView style={styles.content} showsVerticalScrollIndicator={false}> <ScrollView style={styles.content} showsVerticalScrollIndicator={false}>
{/* Goal Type */} {/* Goal Type */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Goal Type *</Text> <Text
<ScrollView horizontal showsHorizontalScrollIndicator={false} style={styles.typeScroll}> style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Goal Type *
</Text>
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
style={styles.typeScroll}
>
{GOAL_TYPES.map((type) => ( {GOAL_TYPES.map((type) => (
<TouchableOpacity <TouchableOpacity
key={type.value} key={type.value}
style={[ style={[
styles.typeButton, styles.typeButton,
goalType === type.value && styles.typeButtonActive, {
borderColor: colors.border,
backgroundColor: colors.surface,
},
goalType === type.value && {
backgroundColor: colors.primary,
borderColor: colors.primary,
},
]} ]}
onPress={() => setGoalType(type.value)} onPress={() => setGoalType(type.value)}
> >
<Text <Text
style={[ style={[
styles.typeButtonText, typography.caption,
goalType === type.value && styles.typeButtonTextActive, { color: colors.textSecondary },
goalType === type.value && {
color: colors.white,
fontWeight: "600",
},
]} ]}
> >
{type.label} {type.label}
@ -137,25 +185,48 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
{/* Title */} {/* Title */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Title *</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Title *
</Text>
<TextInput <TextInput
style={styles.input} style={[
styles.input,
{
backgroundColor: colors.surface,
borderColor: colors.border,
color: colors.textPrimary,
},
]}
value={title} value={title}
onChangeText={setTitle} onChangeText={setTitle}
placeholder="e.g., Lose 5kg" placeholder="e.g., Lose 5kg"
placeholderTextColor="#9ca3af" placeholderTextColor={colors.textTertiary}
/> />
</View> </View>
{/* Description */} {/* Description */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Description</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Description
</Text>
<TextInput <TextInput
style={[styles.input, styles.textArea]} style={[
styles.input,
styles.textArea,
{
backgroundColor: colors.surface,
borderColor: colors.border,
color: colors.textPrimary,
},
]}
value={description} value={description}
onChangeText={setDescription} onChangeText={setDescription}
placeholder="Optional description" placeholder="Optional description"
placeholderTextColor="#9ca3af" placeholderTextColor={colors.textTertiary}
multiline multiline
numberOfLines={3} numberOfLines={3}
/> />
@ -164,63 +235,133 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
{/* Target Value & Unit */} {/* Target Value & Unit */}
<View style={styles.row}> <View style={styles.row}>
<View style={[styles.field, styles.flex1]}> <View style={[styles.field, styles.flex1]}>
<Text style={styles.label}>Target Value</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Target Value
</Text>
<TextInput <TextInput
style={styles.input} style={[
styles.input,
{
backgroundColor: colors.surface,
borderColor: colors.border,
color: colors.textPrimary,
},
]}
value={targetValue} value={targetValue}
onChangeText={setTargetValue} onChangeText={setTargetValue}
placeholder="e.g., 70" placeholder="e.g., 70"
placeholderTextColor="#9ca3af" placeholderTextColor={colors.textTertiary}
keyboardType="numeric" keyboardType="numeric"
/> />
</View> </View>
<View style={[styles.field, styles.flex1]}> <View style={[styles.field, styles.flex1]}>
<Text style={styles.label}>Unit</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Unit
</Text>
<TextInput <TextInput
style={styles.input} style={[
styles.input,
{
backgroundColor: colors.surface,
borderColor: colors.border,
color: colors.textPrimary,
},
]}
value={unit} value={unit}
onChangeText={setUnit} onChangeText={setUnit}
placeholder="e.g., kg" placeholder="e.g., kg"
placeholderTextColor="#9ca3af" placeholderTextColor={colors.textTertiary}
/> />
</View> </View>
</View> </View>
{/* Current Value */} {/* Current Value */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Current Value</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Current Value
</Text>
<TextInput <TextInput
style={styles.input} style={[
styles.input,
{
backgroundColor: colors.surface,
borderColor: colors.border,
color: colors.textPrimary,
},
]}
value={currentValue} value={currentValue}
onChangeText={setCurrentValue} onChangeText={setCurrentValue}
placeholder="Starting value (optional)" placeholder="Starting value (optional)"
placeholderTextColor="#9ca3af" placeholderTextColor={colors.textTertiary}
keyboardType="numeric" keyboardType="numeric"
/> />
</View> </View>
{/* Target Date */} {/* Target Date */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Target Date</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Target Date
</Text>
<TouchableOpacity <TouchableOpacity
style={styles.dateButton} style={[
styles.dateButton,
{
backgroundColor: colors.surface,
borderColor: colors.border,
},
]}
onPress={() => setShowDatePicker(!showDatePicker)} onPress={() => setShowDatePicker(!showDatePicker)}
> >
<Text style={targetDate ? styles.dateText : styles.datePlaceholder}> <Text
{targetDate ? targetDate.toLocaleDateString() : 'Select target date'} style={[
typography.body,
{
color: targetDate
? colors.textPrimary
: colors.textTertiary,
},
]}
>
{targetDate
? targetDate.toLocaleDateString()
: "Select target date"}
</Text> </Text>
<Ionicons name={showDatePicker ? "chevron-up" : "calendar-outline"} size={20} color="#6b7280" /> <Ionicons
name={showDatePicker ? "chevron-up" : "calendar-outline"}
size={20}
color={colors.textSecondary}
/>
</TouchableOpacity> </TouchableOpacity>
</View> </View>
{showDatePicker && ( {showDatePicker && (
<View style={Platform.OS === 'ios' ? styles.datePickerContainer : undefined}> <View
style={
Platform.OS === "ios"
? [
styles.datePickerContainer,
{
backgroundColor: colors.surface,
borderColor: colors.border,
},
]
: undefined
}
>
<DateTimePicker <DateTimePicker
value={targetDate || new Date()} value={targetDate || new Date()}
mode="date" mode="date"
display={Platform.OS === 'ios' ? 'inline' : 'default'} display={Platform.OS === "ios" ? "inline" : "default"}
onChange={(event, selectedDate) => { onChange={(event, selectedDate) => {
setShowDatePicker(Platform.OS === 'ios'); setShowDatePicker(Platform.OS === "ios");
if (selectedDate) { if (selectedDate) {
setTargetDate(selectedDate); setTargetDate(selectedDate);
} }
@ -233,21 +374,36 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
{/* Priority */} {/* Priority */}
<View style={styles.field}> <View style={styles.field}>
<Text style={styles.label}>Priority</Text> <Text
style={[typography.bodyEmphasis, { color: colors.textPrimary }]}
>
Priority
</Text>
<View style={styles.priorityContainer}> <View style={styles.priorityContainer}>
{PRIORITIES.map((p) => ( {PRIORITIES.map((p) => (
<TouchableOpacity <TouchableOpacity
key={p.value} key={p.value}
style={[ style={[
styles.priorityButton, styles.priorityButton,
priority === p.value && { backgroundColor: p.color }, {
borderColor: colors.border,
backgroundColor: colors.surface,
},
priority === p.value && {
backgroundColor: getPriorityColor(p.value),
borderColor: getPriorityColor(p.value),
},
]} ]}
onPress={() => setPriority(p.value)} onPress={() => setPriority(p.value)}
> >
<Text <Text
style={[ style={[
styles.priorityButtonText, typography.body,
priority === p.value && styles.priorityButtonTextActive, { color: colors.textSecondary },
priority === p.value && {
color: colors.white,
fontWeight: "600",
},
]} ]}
> >
{p.label} {p.label}
@ -258,16 +414,23 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
</View> </View>
</ScrollView> </ScrollView>
<View style={styles.footer}> <View
<TouchableOpacity style={[
style={[styles.submitButton, submitting && styles.submitButtonDisabled]} styles.footer,
{
backgroundColor: colors.surface,
borderTopColor: colors.border,
},
]}
>
<MinimalButton
variant="primary"
title={submitting ? "Creating..." : "Create Goal"}
onPress={handleSubmit} onPress={handleSubmit}
disabled={submitting} disabled={submitting}
> loading={submitting}
<Text style={styles.submitButtonText}> style={styles.submitButton}
{submitting ? 'Creating...' : 'Create Goal'} />
</Text>
</TouchableOpacity>
</View> </View>
</View> </View>
</Modal> </Modal>
@ -277,22 +440,14 @@ export function GoalCreationModal({ visible, onClose, onSubmit }: GoalCreationMo
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
flex: 1, flex: 1,
backgroundColor: '#f9fafb',
}, },
header: { header: {
flexDirection: 'row', flexDirection: "row",
justifyContent: 'space-between', justifyContent: "space-between",
alignItems: 'center', alignItems: "center",
padding: 20, padding: 20,
paddingTop: Platform.OS === 'ios' ? 60 : 20, paddingTop: Platform.OS === "ios" ? 60 : 20,
backgroundColor: '#fff',
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#e5e7eb',
},
headerTitle: {
fontSize: 20,
fontWeight: '600',
color: '#111827',
}, },
closeButton: { closeButton: {
padding: 4, padding: 4,
@ -304,27 +459,19 @@ const styles = StyleSheet.create({
field: { field: {
marginBottom: 20, marginBottom: 20,
}, },
label: {
fontSize: 14,
fontWeight: '600',
color: '#374151',
marginBottom: 8,
},
input: { input: {
backgroundColor: '#fff',
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8, borderRadius: 8,
padding: 12, padding: 12,
fontSize: 16, fontSize: 16,
color: '#111827', marginTop: 8,
}, },
textArea: { textArea: {
height: 80, height: 80,
textAlignVertical: 'top', textAlignVertical: "top",
}, },
row: { row: {
flexDirection: 'row', flexDirection: "row",
gap: 12, gap: 12,
}, },
flex1: { flex1: {
@ -332,95 +479,48 @@ const styles = StyleSheet.create({
}, },
typeScroll: { typeScroll: {
flexGrow: 0, flexGrow: 0,
marginTop: 8,
}, },
typeButton: { typeButton: {
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 8, paddingVertical: 8,
borderRadius: 8, borderRadius: 8,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db',
backgroundColor: '#fff',
marginRight: 8, marginRight: 8,
}, },
typeButtonActive: {
backgroundColor: '#2563eb',
borderColor: '#2563eb',
},
typeButtonText: {
fontSize: 14,
color: '#374151',
},
typeButtonTextActive: {
color: '#fff',
fontWeight: '600',
},
dateButton: { dateButton: {
flexDirection: 'row', flexDirection: "row",
justifyContent: 'space-between', justifyContent: "space-between",
alignItems: 'center', alignItems: "center",
backgroundColor: '#fff',
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db',
borderRadius: 8, borderRadius: 8,
padding: 12, padding: 12,
}, marginTop: 8,
dateText: {
fontSize: 16,
color: '#111827',
},
datePlaceholder: {
fontSize: 16,
color: '#9ca3af',
}, },
priorityContainer: { priorityContainer: {
flexDirection: 'row', flexDirection: "row",
gap: 12, gap: 12,
marginTop: 8,
}, },
priorityButton: { priorityButton: {
flex: 1, flex: 1,
paddingVertical: 12, paddingVertical: 12,
borderRadius: 8, borderRadius: 8,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', alignItems: "center",
backgroundColor: '#fff',
alignItems: 'center',
},
priorityButtonText: {
fontSize: 14,
color: '#374151',
fontWeight: '500',
},
priorityButtonTextActive: {
color: '#fff',
fontWeight: '600',
}, },
footer: { footer: {
padding: 20, padding: 20,
paddingBottom: Platform.OS === 'ios' ? 40 : 20, paddingBottom: Platform.OS === "ios" ? 40 : 20,
backgroundColor: '#fff',
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: '#e5e7eb',
}, },
submitButton: { submitButton: {
backgroundColor: '#2563eb', width: "100%",
borderRadius: 8,
padding: 16,
alignItems: 'center',
},
submitButtonDisabled: {
opacity: 0.6,
},
submitButtonText: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
}, },
datePickerContainer: { datePickerContainer: {
backgroundColor: '#fff',
borderRadius: 8, borderRadius: 8,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db',
marginTop: 8, marginTop: 8,
overflow: 'hidden', overflow: "hidden",
}, },
}); });