255 lines
9.6 KiB
TypeScript
255 lines
9.6 KiB
TypeScript
import React, { useState } from "react";
|
|
import {
|
|
View,
|
|
Text,
|
|
StyleSheet,
|
|
ScrollView,
|
|
KeyboardAvoidingView,
|
|
Platform,
|
|
TouchableOpacity,
|
|
Alert,
|
|
Image,
|
|
} from "react-native";
|
|
import { useNavigation } from "@react-navigation/native";
|
|
import { CustomInput } from "../components/common/Custom";
|
|
import CustomButton from "../components/CustomButton";
|
|
import colors from "../components/colors";
|
|
import { launchImageLibrary } from "react-native-image-picker";
|
|
import Ionicons from "@react-native-vector-icons/ionicons";
|
|
import { UPLOAD_GROUP_IMAGE } from "../utils/ApiUrls";
|
|
|
|
export default function GroupCreationForm() {
|
|
const navigation = useNavigation<any>();
|
|
const [form, setForm] = useState({
|
|
groupName: "",
|
|
description: "",
|
|
visibility: "PRIVATE",
|
|
withdrawalMethod: "RANDOM",
|
|
minParticipants: "",
|
|
maxParticipants: "",
|
|
contributionAmount: "",
|
|
cycleType: "MONTHLY",
|
|
customCycleDays: "",
|
|
lateFeeMode: "DAILY",
|
|
lateFeeValue: "",
|
|
});
|
|
|
|
const [errors, setErrors] = useState<any>({});
|
|
const [selectedImage, setSelectedImage] = useState()
|
|
|
|
const handleChange = (key: string, value: string) => {
|
|
setForm({ ...form, [key]: value });
|
|
if (errors[key]) {
|
|
setErrors({ ...errors, [key]: null });
|
|
}
|
|
};
|
|
|
|
const cycleTypes = ["CUSTOM", "MONTHLY", "QUARTERLY", "YEARLY", "HALF_YEARLY"];
|
|
const distributionTypes = ["SEQUENTIAL", "RANDOM"];
|
|
const lateFeeModes = ["DAILY", "MONTHLY"];
|
|
|
|
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) <= 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.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()) {
|
|
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.");
|
|
setErrors('')
|
|
}
|
|
};
|
|
|
|
|
|
return (
|
|
|
|
<KeyboardAvoidingView
|
|
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
|
|
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
|
>
|
|
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
|
|
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View>
|
|
|
|
<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} />
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<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>
|
|
|
|
<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 Group" onPress={handleReview} style={{ marginTop: 20 }} />
|
|
</ScrollView>
|
|
</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: 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,
|
|
},
|
|
|
|
avatarContainer: {
|
|
position: "relative",
|
|
marginBottom: 16,
|
|
},
|
|
avatar: {
|
|
width: 120,
|
|
height: 120,
|
|
borderRadius: 60,
|
|
borderWidth: 4,
|
|
borderColor: "#e2e8f0",
|
|
},
|
|
cameraIcon: {
|
|
position: "absolute",
|
|
right: 0,
|
|
bottom: 0,
|
|
backgroundColor: colors.pastelBlue,
|
|
borderRadius: 20,
|
|
width: 40,
|
|
height: 40,
|
|
justifyContent: "center",
|
|
alignItems: "center",
|
|
borderWidth: 3,
|
|
borderColor: "#fff",
|
|
elevation: 2,
|
|
},
|
|
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: "#374151", fontWeight: "600", textTransform: 'capitalize' },
|
|
selectedButtonText: { color: "#1F2937" },
|
|
|
|
participantsRow: { flexDirection: "row", gap: 15 },
|
|
participantInput: { flex: 1 },
|
|
});
|