errors are there

This commit is contained in:
Abin Toppo
2025-09-19 15:59:31 +05:30
parent 34ab2f0db7
commit 82b121b993
4 changed files with 248 additions and 210 deletions
+150 -135
View File
@@ -7,97 +7,68 @@ import {
KeyboardAvoidingView,
Platform,
TouchableOpacity,
Modal,
Alert,
} from "react-native";
import Slider from "@react-native-community/slider";
import { useNavigation } from "@react-navigation/native";
import { CustomInput } from "../components/common/Custom";
import CustomButton from "../components/CustomButton";
import { Calendar } from "react-native-calendars";
import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from "../components/colors";
const calendarTheme = {
backgroundColor: '#ffffff',
calendarBackground: '#ffffff',
textSectionTitleColor: '#62b59a',
selectedDayTextColor: '#333',
todayTextColor: '#62b59a',
dayTextColor: '#333',
textDisabledColor: '#d9e1e8',
arrowColor: '#62b59a',
monthTextColor: '#333',
textDayFontWeight: '500',
textMonthFontWeight: 'bold',
textDayHeaderFontWeight: '600',
textDayFontSize: 16,
textMonthFontSize: 18,
textDayHeaderFontSize: 14,
};
export default function GroupCreationForm() {
const navigation = useNavigation<any>();
const [form, setForm] = useState({
contributionAmount: "4000",
customCycleDays: "",
cycleType: "MONTHLY",
description: "",
groupName: "",
maxParticipants: "",
minParticipants: "",
startDate: "",
description: "",
visibility: "PRIVATE",
withdrawalMethod: "SEQUENTIAL",
withdrawalMethod: "RANDOM",
minParticipants: "",
maxParticipants: "",
contributionAmount: "",
cycleType: "MONTHLY",
customCycleDays: "",
lateFeeMode: "DAILY",
lateFeeValue: "",
});
const [errors, setErrors] = useState<any>({}); // State for validation errors
const [isCalendarVisible, setCalendarVisible] = useState(false);
const [errors, setErrors] = useState<any>({});
const handleChange = (key: string, value: string) => {
setForm({ ...form, [key]: value });
// Clear error for the field when user starts typing
if (errors[key]) {
setErrors({ ...errors, [key]: null });
}
};
const handleAmountChange = (value: number) => {
setForm({ ...form, contributionAmount: Math.round(value).toString() });
};
const handleDayPress = (day: any) => {
setForm({ ...form, startDate: day.dateString });
setCalendarVisible(false);
setErrors({ ...errors, startDate: null });
};
const presetAmounts = [2000, 4000, 8000];
const cycleTypes = ["CUSTOM", "MONTHLY", "QUARTERLY", "YEARLY"];
const distributionTypes = ["SEQUENTIAL", "RANDOM"];
const handlePresetAmount = (amount: number) => {
setForm({ ...form, contributionAmount: amount.toString() });
};
const lateFeeModes = ["DAILY", "FIXED"];
const validateForm = () => {
const newErrors: any = {};
if (!form.groupName.trim()) newErrors.groupName = "Group name is required.";
if (!form.description.trim()) newErrors.description = "Description is required.";
if (!form.minParticipants || Number(form.minParticipants) <= 0) newErrors.minParticipants = "Min participants must be greater than 0.";
if (!form.minParticipants || Number(form.minParticipants) <= 1) newErrors.minParticipants = "Min participants must be at least 2.";
if (!form.maxParticipants || Number(form.maxParticipants) < Number(form.minParticipants)) newErrors.maxParticipants = "Max must be greater than or equal to min.";
if (!form.contributionAmount || Number(form.contributionAmount) <= 0) newErrors.contributionAmount = "Amount must be greater than 0.";
if (form.cycleType === "CUSTOM" && (!form.customCycleDays || Number(form.customCycleDays) <= 0)) newErrors.customCycleDays = "Custom days must be greater than 0.";
if (!form.startDate) newErrors.startDate = "Start date is required.";
if (form.lateFeeMode && (!form.lateFeeValue || Number(form.lateFeeValue) <= 0)) newErrors.lateFeeValue = "Late fee value is required.";
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleReview = () => {
if (validateForm()) {
console.log("------",form)
navigation.navigate("GroupReview", { form });
const payload = {
...form,
contributionAmount: Number(form.contributionAmount),
customCycleDays: Number(form.customCycleDays) || 0,
lateFeeValue: Number(form.lateFeeValue) || 0,
maxParticipants: Number(form.maxParticipants),
minParticipants: Number(form.minParticipants),
};
console.log("Reviewing Payload:", payload);
navigation.navigate("GroupReview", { form: payload });
} else {
Alert.alert("Incomplete Form", "Please fill out all required fields correctly.");
}
@@ -106,101 +77,145 @@ export default function GroupCreationForm() {
return (
<KeyboardAvoidingView
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
behavior={Platform.OS === "ios" ? "padding" : undefined}
behavior={Platform.OS === "ios" ? "padding" : "height"}
>
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.titleContainer}><Text style={styles.title}>Group Creation</Text></View>
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View>
<CustomInput label="Group Name" placeholder="Enter your group name" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" />
<CustomInput label="Description" placeholder="Enter a short description" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
<CustomInput label="Group Name" placeholder="e.g., 'Family Savings'" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
<CustomInput label="Description" placeholder="e.g., 'Monthly fund for family needs'" value={form.description} onChangeText={(val) => handleChange("description", val)} multiline icon="document-text" error={errors.description} />
<Text style={styles.label}>Visibility</Text>
<View style={styles.toggleContainer}>
<TouchableOpacity style={[styles.toggleButton, form.visibility === "PRIVATE" && styles.selectedButton]} onPress={() => handleChange("visibility", "PRIVATE")}><Text style={[styles.toggleButtonText, form.visibility === "PRIVATE" && styles.selectedButtonText]}>Private</Text></TouchableOpacity>
<TouchableOpacity style={[styles.toggleButton, form.visibility === "PUBLIC" && styles.selectedButton]} onPress={() => handleChange("visibility", "PUBLIC")}><Text style={[styles.toggleButtonText, form.visibility === "PUBLIC" && styles.selectedButtonText]}>Public</Text></TouchableOpacity>
</View>
<Section title="Group Settings">
<SettingRow label="Visibility">
<ToggleButtons
options={["PRIVATE", "PUBLIC"]}
selectedValue={form.visibility}
onSelect={(val) => handleChange("visibility", val)}
/>
</SettingRow>
<SettingRow label="Fund Distribution">
<ToggleButtons
options={distributionTypes}
selectedValue={form.withdrawalMethod}
onSelect={(val) => handleChange("withdrawalMethod", val)}
/>
</SettingRow>
</Section>
<Text style={styles.label}>Fund Distribution</Text>
<View style={styles.toggleContainer}>
{distributionTypes.map((type) => (<TouchableOpacity key={type} onPress={() => handleChange("withdrawalMethod", type)} style={[styles.toggleButton, form.withdrawalMethod === type && styles.selectedButton]}><Text style={[styles.toggleButtonText, form.withdrawalMethod === type && styles.selectedButtonText]}>{type.charAt(0) + type.slice(1).toLowerCase()}</Text></TouchableOpacity>))}
</View>
<View style={styles.participantsRow}>
<View style={styles.participantInput}><CustomInput label="Min" placeholder="Min" value={form.minParticipants} onChangeText={(val) => handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" /></View>
<View style={styles.participantInput}><CustomInput label="Max" placeholder="Max" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" /></View>
</View>
<Section title="Participant Rules">
<View style={styles.participantsRow}>
<View style={styles.participantInput}><CustomInput label="Min" placeholder="e.g., 5" value={form.minParticipants} onChangeText={(val) => handleChange("minParticipants", val)} keyboardType="numeric" icon="person-add" error={errors.minParticipants} /></View>
<View style={styles.participantInput}><CustomInput label="Max" placeholder="e.g., 10" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /></View>
</View>
</Section>
<Text style={styles.label}>Contribution Amount (INR)</Text>
<View style={styles.presetContainer}>
{presetAmounts.map((amount) => (<TouchableOpacity key={amount} onPress={() => handlePresetAmount(amount)} style={[styles.presetButton, form.contributionAmount === amount.toString() && styles.presetSelected]}><Text style={[styles.presetButtonText, form.contributionAmount === amount.toString() && styles.presetSelectedText]}>{amount}</Text></TouchableOpacity>))}
</View>
<View style={styles.amountContainer}>
<Slider style={styles.slider} minimumValue={0} maximumValue={50000} step={100} value={parseInt(form.contributionAmount, 10) || 0} onValueChange={handleAmountChange} minimumTrackTintColor={colors.pastelGreen} maximumTrackTintColor="#ddd" thumbTintColor="#62b59a" />
<View style={styles.amountInput}><CustomInput label="" placeholder="Amount" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" /></View>
</View>
<Text style={styles.label}>Cycle Type</Text>
<View style={styles.cycleTypeContainer}>
{cycleTypes.map((type) => (<TouchableOpacity key={type} onPress={() => handleChange("cycleType", type)} style={[styles.cycleTypeButton, form.cycleType === type && styles.cycleTypeSelected]}><Text style={[styles.cycleTypeButtonText, form.cycleType === type && styles.cycleTypeSelectedText]}>{type.charAt(0) + type.slice(1).toLowerCase()}</Text></TouchableOpacity>))}
</View>
{form.cycleType === "CUSTOM" && (<CustomInput label="Custom Cycle Days" placeholder="Enter number of days" value={form.customCycleDays} onChangeText={(val) => handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" />)}
<Section title="Financial Details">
<CustomInput label="Contribution Amount (INR)" placeholder="e.g., 5000" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} />
<SettingRow label="Payment Cycle">
<ToggleButtons
options={cycleTypes}
selectedValue={form.cycleType}
onSelect={(val) => handleChange("cycleType", val)}
isWrap={true}
/>
</SettingRow>
{form.cycleType === "CUSTOM" && (<CustomInput label="Custom Cycle (Days)" placeholder="Enter number of days" value={form.customCycleDays} onChangeText={(val) => handleChange("customCycleDays", val)} keyboardType="numeric" icon="calendar" error={errors.customCycleDays} />)}
</Section>
<Text style={styles.label}>Start Date</Text>
<TouchableOpacity style={[styles.datePickerButton, errors.startDate && styles.errorBorder]} onPress={() => setCalendarVisible(true)}>
<Ionicons name="calendar-outline" size={20} color="#555" />
<Text style={styles.datePickerText}>{form.startDate || "Select a start date"}</Text>
</TouchableOpacity>
{errors.startDate && <Text style={styles.errorText}>{errors.startDate}</Text>}
<Section title="Late Fee Policy">
<SettingRow label="Fee Mode">
<ToggleButtons
options={lateFeeModes}
selectedValue={form.lateFeeMode}
onSelect={(val) => handleChange("lateFeeMode", val)}
/>
</SettingRow>
<CustomInput label="Late Fee Value (INR)" placeholder="e.g., 50" value={form.lateFeeValue} onChangeText={(val) => handleChange("lateFeeValue", val)} keyboardType="numeric" icon="alert-circle" error={errors.lateFeeValue} />
</Section>
<CustomButton title="Review" onPress={handleReview} />
<CustomButton title="Review Group" onPress={handleReview} style={{marginTop: 20}} />
</ScrollView>
<Modal animationType="fade" transparent={true} visible={isCalendarVisible} onRequestClose={() => setCalendarVisible(false)}>
<TouchableOpacity style={styles.modalBackdrop} activeOpacity={1} onPressOut={() => setCalendarVisible(false)}>
<View style={styles.calendarWrapper}><Calendar onDayPress={handleDayPress} markedDates={{[form.startDate]: { selected: true, selectedColor: '#83f5cc' }}} theme={calendarTheme} /></View>
</TouchableOpacity>
</Modal>
</KeyboardAvoidingView>
);
}
// --- Helper Components for a cleaner layout ---
const Section: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => (
<View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text>
{children}
</View>
);
const SettingRow: React.FC<{ label: string, children: React.ReactNode }> = ({ label, children }) => (
<View style={styles.settingRow}>
<Text style={styles.label}>{label}</Text>
{children}
</View>
);
const ToggleButtons: React.FC<{ options: string[], selectedValue: string, onSelect: (value: string) => void, isWrap?: boolean }> = ({ options, selectedValue, onSelect, isWrap = false }) => (
<View style={[styles.toggleContainer, isWrap && { flexWrap: 'wrap' }]}>
{options.map((option) => (
<TouchableOpacity
key={option}
onPress={() => onSelect(option)}
style={[styles.toggleButton, selectedValue === option && styles.selectedButton, isWrap && {flex: 0, paddingHorizontal: 15}]}
>
<Text style={[styles.toggleButtonText, selectedValue === option && styles.selectedButtonText]}>
{option.charAt(0) + option.slice(1).toLowerCase()}
</Text>
</TouchableOpacity>
))}
</View>
);
const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" },
titleContainer: { paddingVertical: 2, borderRadius: 15, alignSelf: "flex-start", marginBottom: 30 },
title: { fontSize: 22, fontWeight: "bold", color: "#333" },
label: { fontSize: 14, fontWeight: "600", marginBottom: 10, color: "#444" },
toggleContainer: { flexDirection: "row", gap: 10, marginBottom: 15 },
toggleButton: { flex: 1, paddingVertical: 12, borderWidth: 1, borderColor: "#ddd", borderRadius: 12, alignItems: "center", backgroundColor: "#fff" },
container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" },
titleContainer: { marginBottom: 20, paddingHorizontal: 4 },
title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" },
section: {
backgroundColor: '#fff',
borderRadius: 12,
padding: 16,
marginBottom: 16,
elevation: 1,
shadowColor: '#4A5568',
shadowOpacity: 0.06,
shadowRadius: 10,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#374151',
marginBottom: 16,
borderBottomWidth: 1,
borderBottomColor: '#F3F4F6',
paddingBottom: 10,
},
settingRow: {
marginBottom: 16,
},
label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" },
toggleContainer: { flexDirection: "row", gap: 10 },
toggleButton: {
flex: 1,
paddingVertical: 12,
borderWidth: 1,
borderColor: "#D1D5DB",
borderRadius: 8,
alignItems: "center",
backgroundColor: "#F9FAFB"
},
selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" },
toggleButtonText: { color: "#444", fontWeight: "600" },
selectedButtonText: { color: "#333" },
participantsRow: { flexDirection: "row", justifyContent: "space-between", alignItems: 'flex-start', marginBottom: 10 },
participantInput: { flex: 0.48 },
presetContainer: { flexDirection: 'row', justifyContent: 'flex-start', gap: 10, marginBottom: 10 },
presetButton: { paddingVertical: 8, paddingHorizontal: 15, backgroundColor: '#ddd', borderRadius: 20 },
presetSelected: { backgroundColor: colors.pastelGreen, },
presetButtonText: { color: '#555', fontWeight: '600' },
presetSelectedText: { color: '#333' },
amountContainer: { flexDirection: 'row', alignItems: 'center', marginBottom: 15, },
slider: { flex: 1 },
amountInput: { width: 120, marginLeft: 10 },
cycleTypeContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 20 },
cycleTypeButton: { paddingVertical: 10, paddingHorizontal: 15, backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 20, elevation: 1 },
cycleTypeSelected: { backgroundColor: colors.pastelGreen, borderColor: '#62b59a' },
cycleTypeButtonText: { color: '#444', fontWeight: '600', textTransform: 'capitalize' },
cycleTypeSelectedText: { color: '#333' },
datePickerButton: { flexDirection: 'row', alignItems: 'center', backgroundColor: '#fff', borderWidth: 1, borderColor: '#ddd', borderRadius: 12, paddingVertical: 15, paddingHorizontal: 15, marginBottom: 5, },
datePickerText: { marginLeft: 10, fontSize: 16, color: '#333' },
modalBackdrop: { flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0, 0, 0, 0.5)' },
calendarWrapper: { width: '90%', backgroundColor: 'white', borderRadius: 16, paddingVertical: 10, elevation: 5, shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 10, },
// --- ERROR STYLES ---
errorBorder: {
borderColor: '#ff4d4d', // Red border for fields with errors
},
errorText: {
color: '#ff4d4d',
fontSize: 12,
marginBottom: 10,
marginLeft: 5,
},
toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: 'capitalize' },
selectedButtonText: { color: "#1F2937" },
participantsRow: { flexDirection: "row", gap: 15 },
participantInput: { flex: 1 },
});