payment added

This commit is contained in:
Abin Toppo
2025-10-17 17:52:51 +05:30
parent 9093b66a92
commit e5f4cb280d
9 changed files with 420 additions and 180 deletions
+30 -21
View File
@@ -15,6 +15,8 @@ export interface AlertButton {
text: string; text: string;
onPress: () => void; onPress: () => void;
style?: "cancel" | "destructive" | "default" | "primary" | "secondary" | "outline"; style?: "cancel" | "destructive" | "default" | "primary" | "secondary" | "outline";
buttonColor?: string;
textColor?: string;
} }
export interface CustomAlertProps { export interface CustomAlertProps {
@@ -58,7 +60,7 @@ const CustomAlert: React.FC<CustomAlertProps> = ({
} }
}; };
const getButtonStyle = (styleType:string|undefined) => { const getButtonStyle = (styleType: string | undefined) => {
switch (styleType) { switch (styleType) {
case "primary": case "primary":
return [styles.button, styles.primaryButton]; return [styles.button, styles.primaryButton];
@@ -75,7 +77,7 @@ const CustomAlert: React.FC<CustomAlertProps> = ({
} }
}; };
const getButtonTextStyle = (styleType:string|undefined) => { const getButtonTextStyle = (styleType: string | undefined) => {
switch (styleType) { switch (styleType) {
case "primary": case "primary":
return [styles.buttonText, styles.primaryButtonText]; return [styles.buttonText, styles.primaryButtonText];
@@ -110,7 +112,7 @@ const CustomAlert: React.FC<CustomAlertProps> = ({
<View style={styles.faintOverlay} /> <View style={styles.faintOverlay} />
<View style={styles.contentContainer}> <View style={styles.contentContainer}>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
<Ionicons name={icon.name} size={48} color={icon.color} /> <Ionicons name={icon.name} size={36} color={icon.color} /> {/* smaller icon for vertical compression */}
</View> </View>
<Text style={styles.alertTitle}>{title}</Text> <Text style={styles.alertTitle}>{title}</Text>
<Text style={styles.alertMessage}>{message}</Text> <Text style={styles.alertMessage}>{message}</Text>
@@ -119,10 +121,17 @@ const CustomAlert: React.FC<CustomAlertProps> = ({
{buttons.map((button, index) => ( {buttons.map((button, index) => (
<TouchableOpacity <TouchableOpacity
key={index} key={index}
style={getButtonStyle(button.style)} style={[
...getButtonStyle(button.style),
button.style !== "cancel" && button.buttonColor ? { backgroundColor: button.buttonColor } : null,
button.style === "outline" && button.buttonColor ? { borderColor: button.buttonColor } : null,
]}
onPress={button.onPress} onPress={button.onPress}
> >
<Text style={getButtonTextStyle(button.style)}> <Text style={[
...getButtonTextStyle(button.style),
button.style !== "cancel" && button.textColor ? { color: button.textColor } : null,
]}>
{button.text} {button.text}
</Text> </Text>
</TouchableOpacity> </TouchableOpacity>
@@ -150,13 +159,13 @@ const styles = StyleSheet.create({
right: 0, right: 0,
}, },
box: { box: {
paddingHorizontal: 4, paddingHorizontal: 2, // reduce horizontal padding
padding: BORDER_WIDTH + 4, padding: BORDER_WIDTH + 2, // reduce overall padding
borderRadius: RADIUS + BORDER_WIDTH, borderRadius: RADIUS + BORDER_WIDTH,
width: '90%', width: '90%',
maxWidth: 400, maxWidth: 400,
alignSelf: 'center', alignSelf: 'center',
margin: 20, margin: 10, // reduce margin for vertical compression
backgroundColor: colors.pastelPink, backgroundColor: colors.pastelPink,
position: 'relative', position: 'relative',
overflow: 'hidden', overflow: 'hidden',
@@ -169,40 +178,40 @@ const styles = StyleSheet.create({
}, },
contentContainer: { contentContainer: {
zIndex: 2, zIndex: 2,
padding: 24, padding: 12, // reduce container padding
alignItems: "center", alignItems: "center",
}, },
iconContainer: { iconContainer: {
marginBottom: 16, marginBottom: 8, // reduce vertical space below icon
}, },
alertTitle: { alertTitle: {
fontSize: 22, fontSize: 18, // smaller font size
fontWeight: "bold", fontWeight: "bold",
color: colors.textDark, color: colors.textDark,
marginBottom: 8, marginBottom: 4, // less space below title
textAlign: "center", textAlign: "center",
}, },
alertMessage: { alertMessage: {
fontSize: 16, fontSize: 14, // smaller font size
color: colors.textLight, color: colors.textLight,
textAlign: "center", textAlign: "center",
marginBottom: 24, marginBottom: 12, // tighter spacing
lineHeight: 22, lineHeight: 18, // reduced line height
}, },
buttonContainer: { buttonContainer: {
flexDirection: "row", flexDirection: "row",
justifyContent: 'flex-end', justifyContent: 'flex-end',
width: "100%", width: "100%",
gap: 12, gap: 8, // reduce gap between buttons
}, },
button: { button: {
paddingVertical: 14, paddingVertical: 8, // less vertical padding
paddingHorizontal: 24, paddingHorizontal: 16, // less horizontal padding
borderRadius: 16, borderRadius: 16,
justifyContent: 'center', justifyContent: 'center',
alignItems: 'center', alignItems: 'center',
minWidth: 80, minWidth: 72, // slightly smaller minimum width
borderWidth: 0, // default border, override for outline/cancel borderWidth: 0,
}, },
// === BUTTON STYLE VARIANTS === // === BUTTON STYLE VARIANTS ===
defaultButton: { defaultButton: {
@@ -229,7 +238,7 @@ const styles = StyleSheet.create({
}, },
// === BUTTON TEXT COLOR VARIANTS === // === BUTTON TEXT COLOR VARIANTS ===
buttonText: { buttonText: {
fontSize: 16, fontSize: 14, // smaller text
fontWeight: "700", fontWeight: "700",
}, },
defaultButtonText: { defaultButtonText: {
+77 -48
View File
@@ -1,8 +1,6 @@
// src/components/UserRequested.tsx
import React, { FC } from "react"; import React, { FC } from "react";
import { View, Text, StyleSheet } from "react-native"; import { View, Text, StyleSheet } from "react-native";
import {Ionicons} from "@react-native-vector-icons/ionicons"; import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from "./colors"; import colors from "./colors";
import { UserJoinRequest } from "../types"; import { UserJoinRequest } from "../types";
import { formatDate } from "../utils/HelperFunction"; import { formatDate } from "../utils/HelperFunction";
@@ -15,24 +13,23 @@ const getStatusStyle = (status: UserJoinRequest["status"]) => {
switch (status) { switch (status) {
case "APPROVED": case "APPROVED":
return { return {
backgroundColor: "#D1FAE5", // light green backgroundColor: "#D1FAE5",
textColor: "#065F46", // dark green textColor: "#065F46",
label: "Approved", label: "Approved",
icon: "checkmark-circle-outline", icon: "checkmark-circle-outline",
}; };
case "PENDING": case "PENDING":
return { return {
backgroundColor: "#FEF3C7", // light yellow backgroundColor: "#FEF3C7",
textColor: "#92400E", // dark yellow/brown textColor: "#92400E",
label: "Pending", label: "Pending",
icon: "time-outline", icon: "time-outline",
}; };
case "REJECT": case "REJECT":
return { return {
backgroundColor: "#E5E7EB", // light gray backgroundColor: "#FEE2E2",
textColor: "#4B5563", // dark gray textColor: "#B91C1C",
label: "Reject", label: "Rejected",
icon: "close-circle-outline", icon: "close-circle-outline",
}; };
default: default:
@@ -48,38 +45,39 @@ const getStatusStyle = (status: UserJoinRequest["status"]) => {
export const UserRequested: FC<UserRequestedProps> = ({ item }) => { export const UserRequested: FC<UserRequestedProps> = ({ item }) => {
const { backgroundColor, textColor, label, icon } = getStatusStyle(item.status); const { backgroundColor, textColor, label, icon } = getStatusStyle(item.status);
// Optionally, initials avatar (remove/comment if you want):
const initials = item.createdBy?.trim().split(" ").map((w) => w[0])?.join("").toUpperCase();
return ( return (
<View style={styles.card}> <View style={styles.card}>
{/* Top row: User info + Status */}
<View style={styles.topRow}> <View style={styles.topRow}>
<View style={styles.avatarContainer}>
<View style={styles.avatarCircle}>
<Text style={styles.avatarText}>{initials}</Text>
</View>
</View>
<View style={styles.userInfoSection}> <View style={styles.userInfoSection}>
<Text style={styles.groupName}>{item.groupName}</Text> <Text style={styles.groupName}>{item.groupName}</Text>
<Text style={styles.userMeta}> <Text style={styles.userMeta}>
<Ionicons name="time-outline" size={14} color={colors.textLight} />{" "} <Ionicons name="time-outline" size={13} color={colors.textLight} />{" "}
{formatDate(item.requestDate)} {formatDate(item.requestDate)}
</Text> </Text>
</View> </View>
{/* Status Badge */}
<View style={[styles.statusBadge, { backgroundColor }]}> <View style={[styles.statusBadge, { backgroundColor }]}>
<Ionicons name={icon as any} size={16} color={textColor} /> <Ionicons name={icon as any} size={17} color={textColor} />
<Text style={[styles.statusText, { color: textColor }]}>{label}</Text> <Text style={[styles.statusText, { color: textColor }]}>{label}</Text>
</View> </View>
</View> </View>
{/* Group Info */}
<View style={styles.groupInfoSection}> <View style={styles.groupInfoSection}>
<Text style={styles.requestText}> <Text style={styles.requestText}>
Group Created By :{" "} <Ionicons name="person-outline" size={13} color={colors.textLight} />
<Text style={styles.userName} numberOfLines={1}> {" "}Group Created By:{" "}
{item.createdBy} <Text style={styles.userName} numberOfLines={1}>{item.createdBy}</Text>
</Text>
</Text> </Text>
{item.reason ? ( {item.reason ? (
<Text style={styles.reasonText} numberOfLines={2}> <Text style={styles.reasonText} numberOfLines={2}>
Reason: {item.reason} <Ionicons name="chatbubbles-outline" size={13} color={colors.textLight} /> {item.reason}
</Text> </Text>
) : null} ) : null}
</View> </View>
@@ -91,64 +89,95 @@ const styles = StyleSheet.create({
card: { card: {
backgroundColor: colors.white, backgroundColor: colors.white,
borderRadius: 16, borderRadius: 16,
marginVertical: 8, marginVertical: 14, // More space
elevation: 3, marginHorizontal: 10,
elevation: 4,
shadowColor: "#000", shadowColor: "#000",
shadowOpacity: 0.1, shadowOpacity: 0.12,
shadowRadius: 6, shadowRadius: 8,
shadowOffset: { width: 0, height: 3 }, shadowOffset: { width: 0, height: 2 },
padding: 12, padding: 16,
}, },
topRow: { topRow: {
flexDirection: "row", flexDirection: "row",
justifyContent: "space-between",
alignItems: "flex-start", alignItems: "flex-start",
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: colors.Settings, borderBottomColor: "#e5e7eb",
paddingBottom: 8, paddingBottom: 10,
marginBottom: 6
},
avatarContainer: {
marginRight: 10,
marginTop: 2,
},
avatarCircle: {
width: 36,
height: 36,
borderRadius: 18,
backgroundColor: colors.pastelBlue,
justifyContent: "center",
alignItems: "center"
},
avatarText: {
fontWeight: "700",
fontSize: 15,
color: colors.white,
}, },
userInfoSection: { userInfoSection: {
flex: 1, flex: 1,
paddingRight: 8, paddingRight: 8,
}, },
userName: { userName: {
// fontSize: 16,
fontWeight: "bold", fontWeight: "bold",
color: colors.textDark, color: colors.textDark,
fontSize: 15,
}, },
userMeta: { userMeta: {
fontSize: 13, fontSize: 12,
color: colors.textLight, color: colors.textLight,
marginTop: 2, marginTop: 3,
marginBottom: -3,
}, },
groupInfoSection: { groupInfoSection: {
paddingVertical: 12, paddingTop: 10,
paddingBottom: 6,
}, },
requestText: { requestText: {
// fontSize: 15,
color: colors.textDark, color: colors.textDark,
lineHeight: 20, fontSize: 15,
marginBottom: 2,
lineHeight: 22,
}, },
groupName: { groupName: {
fontWeight: "600", fontWeight: "600",
color: colors.Settings, color: colors.Settings,
fontSize:18 fontSize: 18,
marginBottom: 2,
}, },
reasonText: { reasonText: {
fontSize: 13, fontSize: 12,
color: colors.textLight, color: colors.textLight,
marginTop: 4, marginTop: 6,
paddingLeft: 1,
lineHeight: 18,
fontStyle: "italic",
}, },
statusBadge: { statusBadge: {
flexDirection: "row", flexDirection: "row",
alignItems: "center", alignItems: "center",
borderRadius: 12, borderRadius: 10,
paddingHorizontal: 8, paddingHorizontal: 10,
paddingVertical: 4, paddingVertical: 5,
marginLeft: 4,
minWidth: 90,
justifyContent: "center",
alignSelf: "flex-start",
marginTop: 2
}, },
statusText: { statusText: {
fontSize: 13, fontSize: 14,
fontWeight: "600", fontWeight: "700",
marginLeft: 4, marginLeft: 6,
letterSpacing: 0.5
}, },
}); });
+3
View File
@@ -24,6 +24,7 @@ import Notification from "../screens/notification/Notification";
import ManagementScreen from "../screens/management/ManagementScreen"; import ManagementScreen from "../screens/management/ManagementScreen";
import { navigationRef } from "../utils/navigationService"; import { navigationRef } from "../utils/navigationService";
import NotificationRedirection from "../screens/NotificationRedirection"; import NotificationRedirection from "../screens/NotificationRedirection";
// import screen from "../components/test";
export type RootStackParamList = { export type RootStackParamList = {
Splash: undefined; Splash: undefined;
@@ -54,6 +55,7 @@ export type RootStackParamList = {
| { actionTab3: "Your Requests" } | { actionTab3: "Your Requests" }
| { groupId: string | number; } | { groupId: string | number; }
}; };
const Stack = createNativeStackNavigator<RootStackParamList>(); const Stack = createNativeStackNavigator<RootStackParamList>();
@@ -121,3 +123,4 @@ export default function AppNavigator() {
</NavigationContainer> </NavigationContainer>
); );
} }
;
+132 -65
View File
@@ -36,7 +36,7 @@ export default function GroupCreationForm() {
}); });
const [errors, setErrors] = useState<any>({}); const [errors, setErrors] = useState<any>({});
const [selectedImage, setSelectedImage] = useState() const [selectedImage, setSelectedImage] = useState<any>();
const handleChange = (key: string, value: string) => { const handleChange = (key: string, value: string) => {
setForm({ ...form, [key]: value }); setForm({ ...form, [key]: value });
@@ -45,10 +45,37 @@ export default function GroupCreationForm() {
} }
}; };
const cycleTypes = ["CUSTOM", "MONTHLY", "QUARTERLY", "YEARLY", "HALF_YEARLY"]; // ---- Custom logic for Payment Cycle ----
const cycleTypes = [
"CUSTOM",
"DAILY",
"WEEKLY",
"MONTHLY",
"QUARTERLY",
"HALF_YEARLY",
"YEARLY",
];
const distributionTypes = ["SEQUENTIAL", "RANDOM"]; const distributionTypes = ["SEQUENTIAL", "RANDOM"];
const lateFeeModes = ["DAILY", "MONTHLY"]; const lateFeeModes = ["DAILY", "MONTHLY"];
// The core: handle payment cycle options
const handleCycleChange = (val: string) => {
let newForm = { ...form, cycleType: val };
if (val === "DAILY") {
newForm = { ...newForm, cycleType: "CUSTOM", customCycleDays: "1" };
} else if (val === "WEEKLY") {
newForm = { ...newForm, cycleType: "CUSTOM", customCycleDays: "7" };
} else if (
val !== "CUSTOM"
) {
newForm = { ...newForm, customCycleDays: "" }; // Clear custom days
}
setForm(newForm);
if (errors["customCycleDays"]) {
setErrors({ ...errors, customCycleDays: null });
}
};
const validateForm = () => { const validateForm = () => {
const newErrors: any = {}; const newErrors: any = {};
if (!form.groupName.trim()) newErrors.groupName = "Group name is required."; if (!form.groupName.trim()) newErrors.groupName = "Group name is required.";
@@ -58,7 +85,6 @@ export default function GroupCreationForm() {
if (!form.contributionAmount || Number(form.contributionAmount) <= 0) newErrors.contributionAmount = "Amount must be greater than 0."; 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.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."; if (!form.lateFeeMode && (!form.lateFeeValue || Number(form.lateFeeValue) <= 0)) newErrors.lateFeeValue = "Late fee value is required.";
console.log("new errors : ", newErrors)
setErrors(newErrors); setErrors(newErrors);
return Object.keys(newErrors).length === 0; return Object.keys(newErrors).length === 0;
}; };
@@ -73,26 +99,39 @@ export default function GroupCreationForm() {
maxParticipants: Number(form.maxParticipants), maxParticipants: Number(form.maxParticipants),
minParticipants: Number(form.minParticipants), minParticipants: Number(form.minParticipants),
}; };
console.log("Reviewing Payload:", payload); console.log('review payload',payload)
navigation.navigate("GroupReview", { form: payload }); navigation.navigate("GroupReview", { form: payload });
} else { } else {
ToastAndroid.show('Please fill out all required fields correctly.', ToastAndroid.SHORT); ToastAndroid.show('Please fill out all required fields correctly.', ToastAndroid.SHORT);
setErrors('');
} }
}; };
return ( return (
<KeyboardAvoidingView <KeyboardAvoidingView
style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }} style={{ flex: 1, backgroundColor: "#f5f6fa", paddingTop: Platform.OS === "android" ? 40 : 0 }}
behavior={Platform.OS === "ios" ? "padding" : "height"} behavior={Platform.OS === "ios" ? "padding" : "height"}
> >
<ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}> <ScrollView contentContainerStyle={styles.container} showsVerticalScrollIndicator={false}>
<View style={styles.titleContainer}><Text style={styles.title}>Create New Group</Text></View> <View style={styles.titleContainer}>
<Text style={styles.title}>Create New Group</Text>
<CustomInput label="Group Name" placeholder="e.g., 'Family Savings'" value={form.groupName} onChangeText={(val) => handleChange("groupName", val)} icon="people" error={errors.groupName} /> </View>
<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} /> <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"> <Section title="Group Settings">
<SettingRow label="Visibility"> <SettingRow label="Visibility">
<ToggleButtons <ToggleButtons
@@ -109,27 +148,70 @@ export default function GroupCreationForm() {
/> />
</SettingRow> </SettingRow>
</Section> </Section>
<Section title="Participant Rules"> <Section title="Participant Rules">
<View style={styles.participantsRow}> <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}>
<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> <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> </View>
</Section> </Section>
<Section title="Financial Details"> <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} /> <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"> <SettingRow label="Payment Cycle">
<ToggleButtons <ToggleButtons
options={cycleTypes} options={cycleTypes}
selectedValue={form.cycleType} selectedValue={
onSelect={(val) => handleChange("cycleType", val)} form.cycleType === "CUSTOM" && form.customCycleDays === "1"
? "DAILY"
: form.cycleType === "CUSTOM" && form.customCycleDays === "7"
? "WEEKLY"
: form.cycleType
}
onSelect={handleCycleChange}
isWrap={true} isWrap={true}
/> />
</SettingRow> </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} />)} {form.cycleType === "CUSTOM" &&
form.customCycleDays !== "1" &&
form.customCycleDays !== "7" && (
<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>
<Section title="Late Fee Policy"> <Section title="Late Fee Policy">
<SettingRow label="Fee Mode"> <SettingRow label="Fee Mode">
<ToggleButtons <ToggleButtons
@@ -138,9 +220,16 @@ export default function GroupCreationForm() {
onSelect={(val) => handleChange("lateFeeMode", val)} onSelect={(val) => handleChange("lateFeeMode", val)}
/> />
</SettingRow> </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} /> <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> </Section>
<CustomButton title="Review Group" onPress={handleReview} style={{ marginTop: 20 }} /> <CustomButton title="Review Group" onPress={handleReview} style={{ marginTop: 20 }} />
</ScrollView> </ScrollView>
</KeyboardAvoidingView> </KeyboardAvoidingView>
@@ -148,28 +237,36 @@ export default function GroupCreationForm() {
} }
// --- Helper Components for a cleaner layout --- // --- Helper Components for a cleaner layout ---
const Section: React.FC<{ title: string; children: React.ReactNode }> = ({ title, children }) => (
const Section: React.FC<{ title: string, children: React.ReactNode }> = ({ title, children }) => (
<View style={styles.section}> <View style={styles.section}>
<Text style={styles.sectionTitle}>{title}</Text> <Text style={styles.sectionTitle}>{title}</Text>
{children} {children}
</View> </View>
); );
const SettingRow: React.FC<{ label: string, children: React.ReactNode }> = ({ label, children }) => ( const SettingRow: React.FC<{ label: string; children: React.ReactNode }> = ({ label, children }) => (
<View style={styles.settingRow}> <View style={styles.settingRow}>
<Text style={styles.label}>{label}</Text> <Text style={styles.label}>{label}</Text>
{children} {children}
</View> </View>
); );
const ToggleButtons: React.FC<{ options: string[], selectedValue: string, onSelect: (value: string) => void, isWrap?: boolean }> = ({ options, selectedValue, onSelect, isWrap = false }) => ( const ToggleButtons: React.FC<{
<View style={[styles.toggleContainer, isWrap && { flexWrap: 'wrap' }]}> 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) => ( {options.map((option) => (
<TouchableOpacity <TouchableOpacity
key={option} key={option}
onPress={() => onSelect(option)} onPress={() => onSelect(option)}
style={[styles.toggleButton, selectedValue === option && styles.selectedButton, isWrap && { flex: 0, paddingHorizontal: 15 }]} style={[
styles.toggleButton,
selectedValue === option && styles.selectedButton,
isWrap && { flex: 0, paddingHorizontal: 15 },
]}
> >
<Text style={[styles.toggleButtonText, selectedValue === option && styles.selectedButtonText]}> <Text style={[styles.toggleButtonText, selectedValue === option && styles.selectedButtonText]}>
{option.charAt(0) + option.slice(1).toLowerCase()} {option.charAt(0) + option.slice(1).toLowerCase()}
@@ -183,58 +280,29 @@ const styles = StyleSheet.create({
container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" }, container: { flexGrow: 1, padding: 16, backgroundColor: "#f5f6fa" },
titleContainer: { marginBottom: 20, paddingHorizontal: 4 }, titleContainer: { marginBottom: 20, paddingHorizontal: 4 },
title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" }, title: { fontSize: 28, fontWeight: "bold", color: "#1F2937" },
section: { section: {
backgroundColor: '#fff', backgroundColor: "#fff",
borderRadius: 12, borderRadius: 12,
padding: 16, padding: 16,
marginBottom: 16, marginBottom: 16,
elevation: 1, elevation: 1,
shadowColor: '#4A5568', shadowColor: "#4A5568",
shadowOpacity: 0.06, shadowOpacity: 0.06,
shadowRadius: 10, shadowRadius: 10,
}, },
sectionTitle: { sectionTitle: {
fontSize: 18, fontSize: 18,
fontWeight: '600', fontWeight: "600",
color: '#374151', color: "#374151",
marginBottom: 16, marginBottom: 16,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#F3F4F6', borderBottomColor: "#F3F4F6",
paddingBottom: 10, 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: { settingRow: {
marginBottom: 16, marginBottom: 16,
}, },
label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" }, label: { fontSize: 14, fontWeight: "600", marginBottom: 8, color: "#4B5563" },
toggleContainer: { flexDirection: "row", gap: 10 }, toggleContainer: { flexDirection: "row", gap: 10 },
toggleButton: { toggleButton: {
flex: 1, flex: 1,
@@ -243,12 +311,11 @@ const styles = StyleSheet.create({
borderColor: "#D1D5DB", borderColor: "#D1D5DB",
borderRadius: 8, borderRadius: 8,
alignItems: "center", alignItems: "center",
backgroundColor: "#F9FAFB" backgroundColor: "#F9FAFB",
}, },
selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" }, selectedButton: { backgroundColor: colors.pastelGreen, borderColor: "#62b59a" },
toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: 'capitalize' }, toggleButtonText: { color: "#374151", fontWeight: "600", textTransform: "capitalize" },
selectedButtonText: { color: "#1F2937" }, selectedButtonText: { color: "#1F2937" },
participantsRow: { flexDirection: "row", gap: 15 }, participantsRow: { flexDirection: "row", gap: 15 },
participantInput: { flex: 1 }, participantInput: { flex: 1 },
}); });
+95 -36
View File
@@ -6,23 +6,47 @@ import { apiCall } from "../utils/ApiHelper";
import { GROUP_CREATION_URL } from "../utils/ApiUrls"; import { GROUP_CREATION_URL } from "../utils/ApiUrls";
import colors from "../components/colors"; import colors from "../components/colors";
// --- Helper: Get Cycle Label ---
const getCycleLabel = (cycleType: string, customCycleDays: string | number) => {
if (cycleType === "CUSTOM") {
if (String(customCycleDays) === "1") return "Daily";
if (String(customCycleDays) === "7") return "Weekly";
return `Custom (${customCycleDays} days)`;
}
return cycleType.charAt(0).toUpperCase() + cycleType.slice(1).toLowerCase().replace("_", " ");
};
// --- Themed Dialog Component --- // --- Themed Dialog Component ---
const ThemedDialog = ({ visible, isSuccess, message, onClose, onNavigate }: { visible: boolean; isSuccess: boolean; message: string; onClose: () => void; onNavigate: () => void; }) => { const ThemedDialog = ({
visible,
isSuccess,
message,
onClose,
onNavigate,
}: {
visible: boolean;
isSuccess: boolean;
message: string;
onClose: () => void;
onNavigate: () => void;
}) => {
if (!visible) return null; if (!visible) return null;
const config = isSuccess ? { const config = isSuccess
icon: "checkmark-circle" as const, ? {
color: "#2ecc71", icon: "checkmark-circle" as const,
title: "Success!", color: "#2ecc71",
buttonText: "Go Home", title: "Success!",
action: onNavigate, buttonText: "Go Home",
} : { action: onNavigate,
icon: "close-circle" as const, }
color: "#e74c3c", : {
title: "Oops!", icon: "close-circle" as const,
buttonText: "Try Again", color: "#e74c3c",
action: onClose, title: "Oops!",
}; buttonText: "Try Again",
action: onClose,
};
return ( return (
<Modal animationType="fade" transparent={true} visible={visible}> <Modal animationType="fade" transparent={true} visible={visible}>
@@ -49,13 +73,16 @@ export default function GroupReview({ route, navigation }: any) {
const handleNavigateHome = () => { const handleNavigateHome = () => {
setModalVisible(false); setModalVisible(false);
navigation.reset({ index: 0, routes: [{ name: 'Home' }] }); navigation.reset({ index: 0, routes: [{ name: "Home" }] });
}; };
const handleSubmit = async () => { const handleSubmit = async () => {
setIsLoading(true); setIsLoading(true);
try { try {
const res = await apiCall(GROUP_CREATION_URL, "POST", form) as { statusCode?: number, message?: string }; const res = (await apiCall(GROUP_CREATION_URL, "POST", form)) as {
statusCode?: number;
message?: string;
};
if (res?.statusCode === 201) { if (res?.statusCode === 201) {
setIsSuccess(true); setIsSuccess(true);
setFeedbackMessage(res.message || "Your group has been created successfully."); setFeedbackMessage(res.message || "Your group has been created successfully.");
@@ -71,10 +98,16 @@ export default function GroupReview({ route, navigation }: any) {
} }
}; };
const TimelineSection: React.FC<{ icon: any; title: string; children: React.ReactNode }> = ({ icon, title, children }) => ( const TimelineSection: React.FC<{ icon: any; title: string; children: React.ReactNode }> = ({
icon,
title,
children,
}) => (
<View style={styles.section}> <View style={styles.section}>
<View style={styles.timeline}> <View style={styles.timeline}>
<View style={styles.timelineIcon}><Ionicons name={icon} size={22} color="#fff" /></View> <View style={styles.timelineIcon}>
<Ionicons name={icon} size={22} color="#fff" />
</View>
<View style={styles.timelineLine} /> <View style={styles.timelineLine} />
</View> </View>
<View style={styles.content}> <View style={styles.content}>
@@ -121,8 +154,10 @@ export default function GroupReview({ route, navigation }: any) {
<TimelineSection icon="wallet-outline" title="Financial Details"> <TimelineSection icon="wallet-outline" title="Financial Details">
{renderRow("Contribution", `₹${form.contributionAmount}`)} {renderRow("Contribution", `₹${form.contributionAmount}`)}
{renderRow("Cycle", form.cycleType)} {renderRow("Cycle", getCycleLabel(form.cycleType, form.customCycleDays))}
{form.cycleType === "CUSTOM" && renderRow("Cycle Days", `${form.customCycleDays} days`)} {/* Show days only if neither Daily nor Weekly */}
{form.cycleType === "CUSTOM" && !["1", "7"].includes(String(form.customCycleDays))
&& renderRow("Cycle Days", `${form.customCycleDays} days`)}
</TimelineSection> </TimelineSection>
<TimelineSection icon="alert-circle-outline" title="Late Fee Policy"> <TimelineSection icon="alert-circle-outline" title="Late Fee Policy">
@@ -131,7 +166,9 @@ export default function GroupReview({ route, navigation }: any) {
</TimelineSection> </TimelineSection>
<TouchableOpacity onPress={handleSubmit} style={styles.submitBtn} disabled={isLoading}> <TouchableOpacity onPress={handleSubmit} style={styles.submitBtn} disabled={isLoading}>
{isLoading ? <ActivityIndicator color="#333" /> : ( {isLoading ? (
<ActivityIndicator color="#333" />
) : (
<> <>
<Ionicons name="rocket-outline" size={22} color="#333" /> <Ionicons name="rocket-outline" size={22} color="#333" />
<Text style={styles.btnText}>Create Group</Text> <Text style={styles.btnText}>Create Group</Text>
@@ -156,24 +193,46 @@ const styles = StyleSheet.create({
header: { marginBottom: 30 }, header: { marginBottom: 30 },
title: { fontSize: 28, fontWeight: "bold", color: "#333" }, title: { fontSize: 28, fontWeight: "bold", color: "#333" },
subtitle: { fontSize: 16, color: "#666", marginTop: 4 }, subtitle: { fontSize: 16, color: "#666", marginTop: 4 },
section: { flexDirection: 'row', marginBottom: 10 }, section: { flexDirection: "row", marginBottom: 10 },
timeline: { alignItems: 'center', width: 40 }, timeline: { alignItems: "center", width: 40 },
timelineIcon: { width: 40, height: 40, borderRadius: 20, backgroundColor: colors.pastelGreen, justifyContent: 'center', alignItems: 'center', elevation: 3 }, timelineIcon: {
timelineLine: { flex: 1, width: 2, backgroundColor: '#e0e0e0' }, width: 40,
height: 40,
borderRadius: 20,
backgroundColor: colors.pastelGreen,
justifyContent: "center",
alignItems: "center",
elevation: 3,
},
timelineLine: { flex: 1, width: 2, backgroundColor: "#e0e0e0" },
content: { flex: 1, paddingLeft: 10, paddingBottom: 20 }, content: { flex: 1, paddingLeft: 10, paddingBottom: 20 },
sectionTitle: { fontSize: 18, fontWeight: 'bold', color: '#444', marginBottom: 10 }, sectionTitle: { fontSize: 18, fontWeight: "bold", color: "#444", marginBottom: 10 },
card: { backgroundColor: "#fff", borderRadius: 12, padding: 15, elevation: 2 }, card: { backgroundColor: "#fff", borderRadius: 12, padding: 15, elevation: 2 },
row: { flexDirection: "row", justifyContent: "space-between", paddingVertical: 12, borderBottomWidth: 1, borderBottomColor: '#f5f5f5' }, row: {
flexDirection: "row",
justifyContent: "space-between",
paddingVertical: 12,
borderBottomWidth: 1,
borderBottomColor: "#f5f5f5",
},
label: { fontSize: 15, color: "#666", fontWeight: "500" }, label: { fontSize: 15, color: "#666", fontWeight: "500" },
value: { fontSize: 15, color: "#333", fontWeight: "600", textTransform: 'capitalize' }, value: { fontSize: 15, color: "#333", fontWeight: "600", textTransform: "capitalize" },
descriptionContainer: { paddingVertical: 10 }, descriptionContainer: { paddingVertical: 10 },
descriptionText: { fontSize: 15, color: '#333', marginTop: 4, lineHeight: 22 }, descriptionText: { fontSize: 15, color: "#333", marginTop: 4, lineHeight: 22 },
submitBtn: { backgroundColor: colors.pastelGreen, flexDirection: 'row', justifyContent: 'center', alignItems: "center", paddingVertical: 16, borderRadius: 12, marginTop: 20 }, submitBtn: {
backgroundColor: colors.pastelGreen,
flexDirection: "row",
justifyContent: "center",
alignItems: "center",
paddingVertical: 16,
borderRadius: 12,
marginTop: 20,
},
btnText: { color: "#333", fontWeight: "bold", fontSize: 18, marginLeft: 10 }, btnText: { color: "#333", fontWeight: "bold", fontSize: 18, marginLeft: 10 },
modalBackdrop: { flex: 1, backgroundColor: 'rgba(0, 0, 0, 0.6)', justifyContent: 'center', alignItems: 'center' }, modalBackdrop: { flex: 1, backgroundColor: "rgba(0, 0, 0, 0.6)", justifyContent: "center", alignItems: "center" },
dialogBox: { width: '85%', backgroundColor: 'white', borderRadius: 16, padding: 25, alignItems: 'center', elevation: 10 }, dialogBox: { width: "85%", backgroundColor: "white", borderRadius: 16, padding: 25, alignItems: "center", elevation: 10 },
dialogTitle: { fontSize: 22, fontWeight: 'bold', color: '#333', marginTop: 15, marginBottom: 10 }, dialogTitle: { fontSize: 22, fontWeight: "bold", color: "#333", marginTop: 15, marginBottom: 10 },
dialogMessage: { fontSize: 16, color: '#666', textAlign: 'center', marginBottom: 25 }, dialogMessage: { fontSize: 16, color: "#666", textAlign: "center", marginBottom: 25 },
dialogButton: { backgroundColor: '#f0f0f0', paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 }, dialogButton: { backgroundColor: "#f0f0f0", paddingVertical: 12, paddingHorizontal: 40, borderRadius: 10 },
dialogButtonText: { color: '#333', fontWeight: 'bold', fontSize: 16 }, dialogButtonText: { color: "#333", fontWeight: "bold", fontSize: 16 },
}); });
+26 -1
View File
@@ -26,6 +26,7 @@ import { BASE_URL, GET_JOINED_GROUP_URL, JOIN_GROUP_URL, PUBLIC_GROUPS_URL, PUT_
import colors from "../components/colors"; import colors from "../components/colors";
import { ApiResponse } from "./home/UserProfileStatus"; import { ApiResponse } from "./home/UserProfileStatus";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import CustomAlert from "../components/CostomAlart";
const { width } = Dimensions.get('window'); const { width } = Dimensions.get('window');
// --- Types --- // --- Types ---
@@ -113,6 +114,13 @@ export default function GroupScreen() {
const [error, setError] = useState<string | null>(null); const [error, setError] = useState<string | null>(null);
const [role, setRole] = useState<string | null>(null); const [role, setRole] = useState<string | null>(null);
const [refreshing, setRefreshing] = useState<boolean>(false) const [refreshing, setRefreshing] = useState<boolean>(false)
const [alertInfo, setAlertInfo] = useState({
visible: false,
title: '',
message: '',
buttons: [] as { text: string; onPress: () => void; style?: 'cancel' | 'destructive' | 'default' }[],
alertType: 'info' as 'info' | 'success' | 'error',
});
const fetchGroups = async () => { const fetchGroups = async () => {
if (loading) return; if (loading) return;
@@ -177,7 +185,15 @@ export default function GroupScreen() {
console.log("JOIN_GROUP_URL response:", res); console.log("JOIN_GROUP_URL response:", res);
if (res.statusCode === 200) { if (res.statusCode === 200) {
ToastAndroid.show('You requested to join the group.', ToastAndroid.SHORT); setAlertInfo({
visible: true,
title: 'Success',
message: 'Your request to join the group has been sent.',
buttons: [
{ text: 'OK', onPress: () => setAlertInfo({ ...alertInfo, visible: false }), buttonColor: "#28a745", textColor: "#fff" }
],
alertType: 'success',
});
} else { } else {
ToastAndroid.show(res.message || "Failed to join group.", ToastAndroid.SHORT); ToastAndroid.show(res.message || "Failed to join group.", ToastAndroid.SHORT);
} }
@@ -327,6 +343,15 @@ export default function GroupScreen() {
{role === "ORGANIZER" && ( {role === "ORGANIZER" && (
<FAB style={styles.fab} icon={() => <MaterialIcons name="group-add" size={24} color="#333" />} onPress={() => navigation.navigate('GroupCreation')} /> <FAB style={styles.fab} icon={() => <MaterialIcons name="group-add" size={24} color="#333" />} onPress={() => navigation.navigate('GroupCreation')} />
)} )}
<CustomAlert
visible={alertInfo.visible}
title={alertInfo.title}
message={alertInfo.message}
buttons={alertInfo.buttons}
alertType={alertInfo.alertType}
/>
</AppView> </AppView>
); );
} }
+1
View File
@@ -70,6 +70,7 @@ export default function LoginScreen() {
setLoading(true); setLoading(true);
try { try {
const res = await axios.get(LOGIN_URL, { params: { mobile } }); const res = await axios.get(LOGIN_URL, { params: { mobile } });
console.log("login res -",res)
if (res.data.statusCode === 200) { if (res.data.statusCode === 200) {
navigation.navigate('OtpVarify', { mobile }); navigation.navigate('OtpVarify', { mobile });
} else { } else {
+1
View File
@@ -11,6 +11,7 @@ import {
UIManager, UIManager,
Platform, Platform,
Alert, Alert,
ToastAndroid,
} from "react-native"; } from "react-native";
import { Ionicons } from "@react-native-vector-icons/ionicons"; import { Ionicons } from "@react-native-vector-icons/ionicons";
import colors from "../../components/colors"; import colors from "../../components/colors";
+54 -8
View File
@@ -637,14 +637,31 @@ const Profile = () => {
multiline multiline
/> />
<CustomInput <View style={styles.inputContainer}>
error="" <Text style={styles.inputLabel}>Gender</Text>
label="Gender" <View style={styles.genderRow}>
placeholder="Enter gender" {["Male", "Female", "Other"].map((option) => (
value={user?.gender ?? ""} <TouchableOpacity
onChangeText={(text) => setUser({ ...user, gender: text })} key={option}
icon="male-female" style={[
/> styles.genderButton,
user?.gender === option && styles.genderButtonActive,
]}
onPress={() => setUser({ ...user, gender: option })}
>
<Text
style={[
styles.genderButtonText,
user?.gender === option && styles.genderButtonTextActive,
]}
>
{option}
</Text>
</TouchableOpacity>
))}
</View>
</View>
<View style={styles.editButtons}> <View style={styles.editButtons}>
<CustomButton <CustomButton
@@ -869,6 +886,35 @@ const styles = StyleSheet.create({
buttonTextOutline: { buttonTextOutline: {
color: "#6366f1", color: "#6366f1",
}, },
genderRow: {
flexDirection: "row",
justifyContent: "space-between",
marginTop: 8,
marginBottom: 16,
gap: 12,
},
genderButton: {
flex: 1,
paddingVertical: 12,
backgroundColor: "#f1f5f9",
borderRadius: 12,
alignItems: "center",
borderWidth: 1,
borderColor: "#e2e8f0",
},
genderButtonActive: {
backgroundColor: colors.pastelBlue,
},
genderButtonText: {
color: "#64748b",
fontWeight: "500",
fontSize: 16,
},
genderButtonTextActive: {
color: colors.black,
fontWeight: "700"
},
buttonIcon: { buttonIcon: {
marginRight: 8, marginRight: 8,
}, },