group creation themed
This commit is contained in:
+155
-155
@@ -1,36 +1,104 @@
|
||||
import { Picker } from "@react-native-picker/picker";
|
||||
import React, { useState } from "react";
|
||||
import {
|
||||
View,
|
||||
Text,
|
||||
TextInput,
|
||||
TouchableOpacity,
|
||||
StyleSheet,
|
||||
ScrollView,
|
||||
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";
|
||||
|
||||
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: "",
|
||||
contributionAmount: "4000",
|
||||
customCycleDays: "",
|
||||
cycleType: "CUSTOM",
|
||||
cycleType: "MONTHLY",
|
||||
description: "",
|
||||
endDate: "",
|
||||
groupName: "",
|
||||
maxParticipants: "",
|
||||
minParticipants: "",
|
||||
startDate: "",
|
||||
visibility: "PRIVATE",
|
||||
fundDistribution: "SEQUENTIAL",
|
||||
});
|
||||
|
||||
const [errors, setErrors] = useState<any>({}); // State for validation errors
|
||||
const [isCalendarVisible, setCalendarVisible] = useState(false);
|
||||
|
||||
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 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.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.";
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleReview = () => {
|
||||
if (validateForm()) {
|
||||
navigation.navigate("GroupReview", { form });
|
||||
} else {
|
||||
Alert.alert("Incomplete Form", "Please fill out all required fields correctly.");
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -39,166 +107,98 @@ export default function GroupCreationForm() {
|
||||
behavior={Platform.OS === "ios" ? "padding" : undefined}
|
||||
>
|
||||
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
||||
<Text style={styles.title}>Group Creation</Text>
|
||||
|
||||
{/* Group Info */}
|
||||
<Text style={styles.label}>Group Name</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter your group name"
|
||||
value={form.groupName}
|
||||
onChangeText={(val) => handleChange("groupName", val)}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Description</Text>
|
||||
<TextInput
|
||||
style={[styles.input, { height: 80 }]}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter a short description for the group"
|
||||
value={form.description}
|
||||
multiline
|
||||
onChangeText={(val) => handleChange("description", val)}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Visibility</Text>
|
||||
<View style={styles.pickerWrapper}>
|
||||
<Picker
|
||||
selectedValue={form.visibility}
|
||||
onValueChange={(val) => handleChange("visibility", val)}
|
||||
style={styles.picker}
|
||||
>
|
||||
<Picker.Item label="Private (Only invited can join)" value="PRIVATE" />
|
||||
<Picker.Item label="Public (Anyone can find)" value="PUBLIC" />
|
||||
</Picker>
|
||||
</View>
|
||||
|
||||
{/* Participants */}
|
||||
<Text style={styles.label}>Minimum Participants</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter minimum number of participants"
|
||||
keyboardType="numeric"
|
||||
value={form.minParticipants}
|
||||
onChangeText={(val) => handleChange("minParticipants", val)}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Maximum Participants</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter maximum number of participants"
|
||||
keyboardType="numeric"
|
||||
value={form.maxParticipants}
|
||||
onChangeText={(val) => handleChange("maxParticipants", val)}
|
||||
/>
|
||||
|
||||
{/* Contribution */}
|
||||
<Text style={styles.label}>Contribution Amount (₹)</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter contribution amount (₹)"
|
||||
keyboardType="numeric"
|
||||
value={form.contributionAmount}
|
||||
onChangeText={(val) => handleChange("contributionAmount", val)}
|
||||
/>
|
||||
|
||||
<Text style={styles.label}>Cycle Type</Text>
|
||||
<View style={styles.pickerWrapper}>
|
||||
<Picker
|
||||
selectedValue={form.cycleType}
|
||||
onValueChange={(val) => handleChange("cycleType", val)}
|
||||
style={styles.picker}
|
||||
>
|
||||
<Picker.Item label="Custom (Set custom cycle days)" value="CUSTOM" />
|
||||
<Picker.Item label="Monthly" value="MONTHLY" />
|
||||
<Picker.Item label="Quarterly" value="QUARTERLY" />
|
||||
<Picker.Item label="Half Yearly" value="HALF_YEARLY" />
|
||||
<Picker.Item label="Yearly" value="YEARLY" />
|
||||
<View style={styles.titleContainer}><Text style={styles.title}>Group Creation</Text></View>
|
||||
|
||||
</Picker>
|
||||
<CustomInput label="Group Name" placeholder="Enter your group name" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} />
|
||||
<CustomInput label="Description" placeholder="Enter a short description" 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>
|
||||
|
||||
{form.cycleType === "CUSTOM" && (
|
||||
<>
|
||||
<Text style={styles.label}>Custom Cycle Days</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="Enter number of days for custom cycle"
|
||||
keyboardType="numeric"
|
||||
value={form.customCycleDays}
|
||||
onChangeText={(val) => handleChange("customCycleDays", val)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<Text style={styles.label}>Fund Distribution</Text>
|
||||
<View style={styles.toggleContainer}>
|
||||
{distributionTypes.map((type) => (<TouchableOpacity key={type} onPress={() => handleChange("fundDistribution", type)} style={[styles.toggleButton, form.fundDistribution === type && styles.selectedButton]}><Text style={[styles.toggleButtonText, form.fundDistribution === 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" error={errors.minParticipants} /></View>
|
||||
<View style={styles.participantInput}><CustomInput label="Max" placeholder="Max" value={form.maxParticipants} onChangeText={(val) => handleChange("maxParticipants", val)} keyboardType="numeric" icon="people-circle" error={errors.maxParticipants} /></View>
|
||||
</View>
|
||||
|
||||
<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="#83f5cc" maximumTrackTintColor="#ddd" thumbTintColor="#62b59a" />
|
||||
<View style={styles.amountInput}><CustomInput label="" placeholder="Amount" value={form.contributionAmount} onChangeText={(val) => handleChange("contributionAmount", val)} keyboardType="numeric" icon="cash" error={errors.contributionAmount} /></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" error={errors.customCycleDays} />)}
|
||||
|
||||
{/* Timeline */}
|
||||
<Text style={styles.label}>Start Date</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholder="YYYY-MM-DD (e.g. 2025-09-12)"
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
value={form.startDate}
|
||||
onChangeText={(val) => handleChange("startDate", val)}
|
||||
/>
|
||||
<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>}
|
||||
|
||||
<Text style={styles.label}>End Date</Text>
|
||||
<TextInput
|
||||
style={styles.input}
|
||||
placeholderTextColor={"#a3a3a3ff"}
|
||||
placeholder="YYYY-MM-DD (e.g. 2025-12-31)"
|
||||
value={form.endDate}
|
||||
onChangeText={(val) => handleChange("endDate", val)}
|
||||
/>
|
||||
|
||||
{/* Next Button
|
||||
<TouchableOpacity
|
||||
onPress={() => navigation.navigate("GroupReview", { form })}
|
||||
style={styles.nextBtn}
|
||||
>
|
||||
<Text style={styles.btnText}>➡ Review</Text>
|
||||
</TouchableOpacity> */}
|
||||
<CustomButton
|
||||
title="➡ Review"
|
||||
onPress={() => navigation.navigate("GroupReview", { form })}
|
||||
/>
|
||||
<CustomButton title="Review" onPress={handleReview} />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: { flexGrow: 1, padding: 20 },
|
||||
title: { fontSize: 22, fontWeight: "bold", marginBottom: 15, color: "#333" },
|
||||
label: { fontSize: 14, fontWeight: "600", marginBottom: 5, color: "#444" },
|
||||
input: {
|
||||
borderWidth: 1,
|
||||
borderColor: "#ddd",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
marginBottom: 15,
|
||||
fontSize: 16,
|
||||
backgroundColor: "#fff",
|
||||
elevation: 1,
|
||||
container: { flexGrow: 1, padding: 20, backgroundColor: "#f5f6fa" },
|
||||
titleContainer: { backgroundColor: "#83f5cc", paddingHorizontal: 18, 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" },
|
||||
selectedButton: { backgroundColor: "#83f5cc", 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: '#83f5cc' },
|
||||
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: '#83f5cc', 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
|
||||
},
|
||||
pickerWrapper: {
|
||||
borderWidth: 1,
|
||||
borderColor: "#ddd",
|
||||
borderRadius: 12,
|
||||
marginBottom: 15,
|
||||
backgroundColor: "#fff",
|
||||
elevation: 1,
|
||||
errorText: {
|
||||
color: '#ff4d4d',
|
||||
fontSize: 12,
|
||||
marginBottom: 10,
|
||||
marginLeft: 5,
|
||||
},
|
||||
picker: { height: 60, width: "100%" },
|
||||
nextBtn: {
|
||||
backgroundColor: "#eb87a3ff",
|
||||
paddingVertical: 16,
|
||||
borderRadius: 12,
|
||||
alignItems: "center",
|
||||
marginTop: 10,
|
||||
},
|
||||
btnText: { color: "#fff", fontWeight: "bold", fontSize: 16 },
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user