Requeste api implelented
This commit is contained in:
Generated
+16
@@ -26,6 +26,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.1",
|
"react-native": "0.81.1",
|
||||||
"react-native-calendars": "^1.1313.0",
|
"react-native-calendars": "^1.1313.0",
|
||||||
|
"react-native-dialog": "^9.3.0",
|
||||||
"react-native-elements": "^3.4.3",
|
"react-native-elements": "^3.4.3",
|
||||||
"react-native-image-picker": "^8.2.1",
|
"react-native-image-picker": "^8.2.1",
|
||||||
"react-native-keychain": "^10.0.0",
|
"react-native-keychain": "^10.0.0",
|
||||||
@@ -4803,6 +4804,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
|
||||||
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
"integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"balanced-match": "^1.0.0"
|
"balanced-match": "^1.0.0"
|
||||||
@@ -8099,6 +8101,7 @@
|
|||||||
"version": "2.0.0",
|
"version": "2.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/istanbul-lib-coverage": {
|
"node_modules/istanbul-lib-coverage": {
|
||||||
@@ -8855,6 +8858,7 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
|
||||||
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
"integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/json-schema-traverse": {
|
"node_modules/json-schema-traverse": {
|
||||||
@@ -9786,6 +9790,7 @@
|
|||||||
"version": "9.0.5",
|
"version": "9.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
|
||||||
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
|
||||||
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"brace-expansion": "^2.0.1"
|
"brace-expansion": "^2.0.1"
|
||||||
@@ -13256,6 +13261,15 @@
|
|||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-native-dialog": {
|
||||||
|
"version": "9.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-native-dialog/-/react-native-dialog-9.3.0.tgz",
|
||||||
|
"integrity": "sha512-JEOJY/0AzTM9grIl0BL8o/IJPIJru7k5MPj9POTE9RRezUEtgn0YSvCpTlBtG0obWgOdzg2otMz1OQvMXS0wMQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react-native": ">=0.63.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-native-elements": {
|
"node_modules/react-native-elements": {
|
||||||
"version": "3.4.3",
|
"version": "3.4.3",
|
||||||
"resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz",
|
"resolved": "https://registry.npmjs.org/react-native-elements/-/react-native-elements-3.4.3.tgz",
|
||||||
@@ -14785,6 +14799,7 @@
|
|||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||||
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
"integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
|
||||||
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/throat": {
|
"node_modules/throat": {
|
||||||
@@ -15243,6 +15258,7 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||||
|
"devOptional": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"isexe": "^2.0.0"
|
"isexe": "^2.0.0"
|
||||||
|
|||||||
@@ -28,6 +28,7 @@
|
|||||||
"react": "19.1.0",
|
"react": "19.1.0",
|
||||||
"react-native": "0.81.1",
|
"react-native": "0.81.1",
|
||||||
"react-native-calendars": "^1.1313.0",
|
"react-native-calendars": "^1.1313.0",
|
||||||
|
"react-native-dialog": "^9.3.0",
|
||||||
"react-native-elements": "^3.4.3",
|
"react-native-elements": "^3.4.3",
|
||||||
"react-native-image-picker": "^8.2.1",
|
"react-native-image-picker": "^8.2.1",
|
||||||
"react-native-keychain": "^10.0.0",
|
"react-native-keychain": "^10.0.0",
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const colors = {
|
|||||||
textLight: "#555555",
|
textLight: "#555555",
|
||||||
white: "#FFFFFF",
|
white: "#FFFFFF",
|
||||||
black: "#000000",
|
black: "#000000",
|
||||||
primary: "#007AFF",
|
primary: "#a57ae9ff",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default colors;
|
export default colors;
|
||||||
|
|||||||
+29
-5
@@ -3,20 +3,44 @@ import type { PayloadAction } from "@reduxjs/toolkit";
|
|||||||
|
|
||||||
export interface UserState {
|
export interface UserState {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
userId: number | null;
|
||||||
|
username: string;
|
||||||
|
firstName: string;
|
||||||
|
lastName: string;
|
||||||
|
gender: string;
|
||||||
|
mobile: string;
|
||||||
email: string;
|
email: string;
|
||||||
|
address: string;
|
||||||
|
bankAccountNumber: string;
|
||||||
|
bankName: string;
|
||||||
|
branchCode: string;
|
||||||
profilePicture: string;
|
profilePicture: string;
|
||||||
|
filePath: string;
|
||||||
|
imageBlob: string;
|
||||||
role: string;
|
role: string;
|
||||||
|
organizerId: string;
|
||||||
organizerStatus: string;
|
organizerStatus: string;
|
||||||
profileStatus: boolean;
|
profileStatus: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialState: UserState = {
|
const initialState: UserState = {
|
||||||
id: "",
|
id: "",
|
||||||
name: "",
|
userId: null,
|
||||||
|
username: "",
|
||||||
|
firstName: "",
|
||||||
|
lastName: "",
|
||||||
|
gender: "",
|
||||||
|
mobile: "",
|
||||||
email: "",
|
email: "",
|
||||||
|
address: "",
|
||||||
|
bankAccountNumber: "",
|
||||||
|
bankName: "",
|
||||||
|
branchCode: "",
|
||||||
profilePicture: "",
|
profilePicture: "",
|
||||||
|
filePath: "",
|
||||||
|
imageBlob: "",
|
||||||
role: "",
|
role: "",
|
||||||
|
organizerId: "",
|
||||||
organizerStatus: "",
|
organizerStatus: "",
|
||||||
profileStatus: false,
|
profileStatus: false,
|
||||||
};
|
};
|
||||||
@@ -26,12 +50,12 @@ export const userSlice = createSlice({
|
|||||||
initialState,
|
initialState,
|
||||||
reducers: {
|
reducers: {
|
||||||
// ✅ set all user details at once (e.g. after login)
|
// ✅ set all user details at once (e.g. after login)
|
||||||
setUser: (state, action: PayloadAction<UserState>) => {
|
setUserData: (state, action: PayloadAction<UserState>) => {
|
||||||
return { ...action.payload };
|
return { ...action.payload };
|
||||||
},
|
},
|
||||||
|
|
||||||
// ✅ update only selected fields (e.g. profile update)
|
// ✅ update only selected fields (e.g. profile update)
|
||||||
updateUser: (state, action: PayloadAction<Partial<UserState>>) => {
|
updateUserData: (state, action: PayloadAction<Partial<UserState>>) => {
|
||||||
return { ...state, ...action.payload };
|
return { ...state, ...action.payload };
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -41,6 +65,6 @@ export const userSlice = createSlice({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Action creators
|
// Action creators
|
||||||
export const { setUser, updateUser, clearUser } = userSlice.actions;
|
export const { setUserData, updateUserData, clearUser } = userSlice.actions;
|
||||||
|
|
||||||
export default userSlice.reducer;
|
export default userSlice.reducer;
|
||||||
|
|||||||
+20
-24
@@ -10,24 +10,29 @@ import {
|
|||||||
Platform,
|
Platform,
|
||||||
Animated,
|
Animated,
|
||||||
StatusBar,
|
StatusBar,
|
||||||
|
ActivityIndicator,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { LOGIN_URL } from "../utils/ApiUrls";
|
import { LOGIN_URL } from "../utils/ApiUrls";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import type { StackNavigationProp } from '@react-navigation/stack';
|
import type { StackNavigationProp } from "@react-navigation/stack";
|
||||||
import { RootStackParamList } from "../navigation/AppNavigator";
|
import { RootStackParamList } from "../navigation/AppNavigator";
|
||||||
|
import { CustomButton, CustomInput } from "../components/common/Custom";
|
||||||
|
import colors from "../components/colors";
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
primary: "#111111",
|
primary: "#111111",
|
||||||
accent: "#FF7A00",
|
accent: colors.primary,
|
||||||
background: "#F9F9F9",
|
background: "#F9F9F9",
|
||||||
|
surface: "#FFFFFF",
|
||||||
text: "#1A1A1A",
|
text: "#1A1A1A",
|
||||||
subText: "#6B6B6B",
|
subText: "#6B6B6B",
|
||||||
outline: "#E5E5E5",
|
outline: "#E5E5E5",
|
||||||
|
error: "#E53935",
|
||||||
|
success: "#2E7D32",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function LoginScreen() {
|
export default function LoginScreen() {
|
||||||
|
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
|
||||||
const [mobile, setMobile] = useState("");
|
const [mobile, setMobile] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
@@ -48,16 +53,14 @@ export default function LoginScreen() {
|
|||||||
if (mobile.length === 10) {
|
if (mobile.length === 10) {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
console.log("Mobile submitted:", mobile);
|
const res = await axios.get(LOGIN_URL + mobile);
|
||||||
const res = await axios.get(LOGIN_URL + mobile)
|
|
||||||
console.log('loginres', res.data)
|
|
||||||
if (res.data.statusCode === 200) {
|
if (res.data.statusCode === 200) {
|
||||||
navigation.navigate('OtpVarify', { mobile: mobile })
|
navigation.navigate("OtpVarify", { mobile });
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Login error:", error);
|
console.error("Login error:", error);
|
||||||
Alert.alert("Error", "Failed to send OTP. Please try again.");
|
Alert.alert("Error", "Failed to send OTP. Please try again.");
|
||||||
}finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -102,15 +105,14 @@ export default function LoginScreen() {
|
|||||||
{ opacity: fade, transform: [{ translateY: slide }] },
|
{ opacity: fade, transform: [{ translateY: slide }] },
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Text style={styles.inputLabel}>Mobile Number</Text>
|
<CustomInput
|
||||||
<TextInput
|
label="Mobile Number"
|
||||||
style={styles.input}
|
|
||||||
placeholder="Enter Mobile Number"
|
placeholder="Enter Mobile Number"
|
||||||
placeholderTextColor={theme.subText}
|
|
||||||
keyboardType="phone-pad"
|
|
||||||
maxLength={10}
|
|
||||||
value={mobile}
|
value={mobile}
|
||||||
onChangeText={setMobile}
|
onChangeText={setMobile}
|
||||||
|
keyboardType="phone-pad"
|
||||||
|
maxLength={10}
|
||||||
|
icon="call-outline"
|
||||||
/>
|
/>
|
||||||
</Animated.View>
|
</Animated.View>
|
||||||
</View>
|
</View>
|
||||||
@@ -119,14 +121,8 @@ export default function LoginScreen() {
|
|||||||
<Animated.View
|
<Animated.View
|
||||||
style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]}
|
style={[styles.bottomSection, { transform: [{ scale: ctaScale }] }]}
|
||||||
>
|
>
|
||||||
<TouchableOpacity
|
<CustomButton title={loading ? "Loading..." : "Continue"} onPress={handleContinue} icon="arrow-forward" />
|
||||||
style={styles.button}
|
|
||||||
onPress={handleContinue}
|
|
||||||
onPressIn={onPressIn}
|
|
||||||
onPressOut={onPressOut}
|
|
||||||
>
|
|
||||||
<Text style={styles.buttonText}>Continue</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<Text style={styles.footerText}>
|
<Text style={styles.footerText}>
|
||||||
By continuing, you agree to our{" "}
|
By continuing, you agree to our{" "}
|
||||||
<Text style={{ color: theme.accent, fontWeight: "600" }}>
|
<Text style={{ color: theme.accent, fontWeight: "600" }}>
|
||||||
@@ -142,7 +138,7 @@ const styles = StyleSheet.create({
|
|||||||
container: { flex: 1, backgroundColor: theme.background, padding: 20 },
|
container: { flex: 1, backgroundColor: theme.background, padding: 20 },
|
||||||
topSection: { flex: 1, justifyContent: "center" },
|
topSection: { flex: 1, justifyContent: "center" },
|
||||||
brand: {
|
brand: {
|
||||||
fontSize: 20,
|
fontSize: 30,
|
||||||
fontWeight: "700",
|
fontWeight: "700",
|
||||||
color: theme.accent,
|
color: theme.accent,
|
||||||
marginBottom: 24,
|
marginBottom: 24,
|
||||||
@@ -166,7 +162,7 @@ const styles = StyleSheet.create({
|
|||||||
color: theme.text,
|
color: theme.text,
|
||||||
},
|
},
|
||||||
input: {
|
input: {
|
||||||
backgroundColor: "#fff",
|
backgroundColor: theme.surface,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
padding: 14,
|
padding: 14,
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
|
|||||||
@@ -20,10 +20,11 @@ import { RootStackParamList } from "../navigation/AppNavigator";
|
|||||||
import * as Keychain from "react-native-keychain";
|
import * as Keychain from "react-native-keychain";
|
||||||
import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
|
import { LOGIN_URL, VARIFY_OTP_URL } from "../utils/ApiUrls";
|
||||||
import { StackNavigationProp } from "@react-navigation/stack";
|
import { StackNavigationProp } from "@react-navigation/stack";
|
||||||
|
import colors from "../components/colors";
|
||||||
|
|
||||||
const theme = {
|
const theme = {
|
||||||
primary: "#111111",
|
primary: "#111111",
|
||||||
accent: "#FF7A00",
|
accent: colors.primary,
|
||||||
background: "#F9F9F9",
|
background: "#F9F9F9",
|
||||||
text: "#1A1A1A",
|
text: "#1A1A1A",
|
||||||
subText: "#6B6B6B",
|
subText: "#6B6B6B",
|
||||||
@@ -245,13 +246,13 @@ const styles = StyleSheet.create({
|
|||||||
cellFilled: { borderColor: "#D1D1D1" },
|
cellFilled: { borderColor: "#D1D1D1" },
|
||||||
bottomSection: { marginBottom: 30 },
|
bottomSection: { marginBottom: 30 },
|
||||||
button: {
|
button: {
|
||||||
backgroundColor: theme.primary,
|
backgroundColor: colors.primary,
|
||||||
paddingVertical: 18,
|
paddingVertical: 18,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
buttonText: { color: "#fff", fontWeight: "700", fontSize: 16 },
|
buttonText: { color: "#000", fontWeight: "700", fontSize: 16 },
|
||||||
footerText: {
|
footerText: {
|
||||||
textAlign: "center",
|
textAlign: "center",
|
||||||
color: theme.subText,
|
color: theme.subText,
|
||||||
|
|||||||
@@ -1,13 +1,75 @@
|
|||||||
import React, { useEffect, useRef } from "react";
|
import React, { useEffect, useRef } from "react";
|
||||||
import { View, Text, StyleSheet, ActivityIndicator, Animated } from "react-native";
|
import { View, Text, StyleSheet, ActivityIndicator, Animated, Alert } from "react-native";
|
||||||
import * as Keychain from "react-native-keychain";
|
import * as Keychain from "react-native-keychain";
|
||||||
import { StackNavigationProp } from "@react-navigation/stack";
|
import { StackNavigationProp } from "@react-navigation/stack";
|
||||||
import { useNavigation } from "@react-navigation/native";
|
import { useNavigation } from "@react-navigation/native";
|
||||||
import { RootStackParamList } from "../navigation/AppNavigator"; // adjust path
|
import { RootStackParamList } from "../navigation/AppNavigator"; // adjust path
|
||||||
|
import { BASE_URL } from "../utils/ApiUrls";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import { setUserData } from "../redux/userSlice";
|
||||||
|
import colors from "../components/colors";
|
||||||
|
|
||||||
const SplashScreen = () => {
|
const SplashScreen = () => {
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const user = useSelector((state: any) => state.user);
|
||||||
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
|
const navigation = useNavigation<StackNavigationProp<RootStackParamList>>();
|
||||||
const fadeAnim = useRef(new Animated.Value(1)).current; // opacity starts at 1
|
const fadeAnim = useRef(new Animated.Value(1)).current;
|
||||||
|
|
||||||
|
const fetchUserProfile = async () => {
|
||||||
|
try {
|
||||||
|
const credentials = await Keychain.getGenericPassword();
|
||||||
|
if (!credentials) {
|
||||||
|
Alert.alert("Error", "You are not logged in");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = credentials.password;
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`${BASE_URL}user/profile/view`,
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
Accept: "*/*",
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
if (result.status === "success") {
|
||||||
|
dispatch(setUserData({
|
||||||
|
id: result?.data.id,
|
||||||
|
userId: result?.data.userId,
|
||||||
|
username: result?.data.username,
|
||||||
|
firstName: result?.data.firstName,
|
||||||
|
lastName: result?.data.lastName,
|
||||||
|
gender: result?.data.gender,
|
||||||
|
mobile: result?.data.mobile,
|
||||||
|
email: result?.data.email,
|
||||||
|
address: result?.data.address,
|
||||||
|
bankAccountNumber: result?.data.bankAccountNumber,
|
||||||
|
bankName: result?.data.bankName,
|
||||||
|
branchCode: result?.data.branchCode,
|
||||||
|
profilePicture: result?.data.filePath, // or keep as `filePath`
|
||||||
|
filePath: result?.data.filePath,
|
||||||
|
imageBlob: result?.data.imageBlob,
|
||||||
|
role: result?.data.role,
|
||||||
|
organizerId: result?.data.organizerId,
|
||||||
|
organizerStatus: result?.data.organizerStatus,
|
||||||
|
profileStatus: true, // set true once profile is fetched
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log("splash user data:", result.data);
|
||||||
|
} else {
|
||||||
|
Alert.alert("Error", "Failed to fetch user profile");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("❌ Error fetching profile:", error);
|
||||||
|
Alert.alert("Error", "Could not connect to server");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("redux user", user);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const checkLogin = async () => {
|
const checkLogin = async () => {
|
||||||
@@ -36,12 +98,16 @@ const SplashScreen = () => {
|
|||||||
});
|
});
|
||||||
}, [navigation, fadeAnim]);
|
}, [navigation, fadeAnim]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchUserProfile();
|
||||||
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<Animated.Text style={[styles.title, { opacity: fadeAnim }]}>
|
<Animated.Text style={[styles.title, { opacity: fadeAnim }]}>
|
||||||
Circle Collect
|
Circle Collect
|
||||||
</Animated.Text>
|
</Animated.Text>
|
||||||
<ActivityIndicator size="large" color="#FF7A00" style={{ marginTop: 20 }} />
|
<ActivityIndicator size="large" color={colors.Home} style={{ marginTop: 20 }} />
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -58,7 +124,7 @@ const styles = StyleSheet.create({
|
|||||||
title: {
|
title: {
|
||||||
fontSize: 28,
|
fontSize: 28,
|
||||||
fontWeight: "800",
|
fontWeight: "800",
|
||||||
color: "#FF7A00",
|
color: colors.Home,
|
||||||
letterSpacing: 1,
|
letterSpacing: 1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { View, Text, TextInput, Button, Modal, StyleSheet } from "react-native";
|
||||||
|
|
||||||
|
const DeclineModal = ({ visible, onClose, onSubmit }: {
|
||||||
|
visible: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
onSubmit: (reason: string) => void;
|
||||||
|
}) => {
|
||||||
|
const [reason, setReason] = useState("");
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Modal
|
||||||
|
transparent
|
||||||
|
animationType="fade"
|
||||||
|
visible={visible}
|
||||||
|
onRequestClose={onClose}
|
||||||
|
>
|
||||||
|
<View style={styles.overlay}>
|
||||||
|
<View style={styles.container}>
|
||||||
|
<Text style={styles.title}>Decline Request</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
placeholder="Enter reason"
|
||||||
|
value={reason}
|
||||||
|
onChangeText={setReason}
|
||||||
|
/>
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<Button title="Cancel" onPress={onClose} />
|
||||||
|
<Button
|
||||||
|
title="Decline"
|
||||||
|
onPress={() => {
|
||||||
|
onSubmit(reason);
|
||||||
|
setReason(""); // reset after submit
|
||||||
|
}}
|
||||||
|
color="red"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Modal>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
overlay: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: "center",
|
||||||
|
alignItems: "center",
|
||||||
|
backgroundColor: "rgba(0,0,0,0.5)",
|
||||||
|
},
|
||||||
|
container: {
|
||||||
|
width: "80%",
|
||||||
|
backgroundColor: "white",
|
||||||
|
padding: 20,
|
||||||
|
borderRadius: 10,
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
fontSize: 18,
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
input: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: "#ccc",
|
||||||
|
borderRadius: 5,
|
||||||
|
padding: 8,
|
||||||
|
marginBottom: 15,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
flexDirection: "row",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DeclineModal;
|
||||||
@@ -2,42 +2,38 @@ import React, { useState, useEffect } from "react";
|
|||||||
import {
|
import {
|
||||||
View,
|
View,
|
||||||
Text,
|
Text,
|
||||||
TextInput,
|
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
|
TouchableOpacity,
|
||||||
|
TextInput,
|
||||||
FlatList,
|
FlatList,
|
||||||
ActivityIndicator,
|
ActivityIndicator,
|
||||||
TouchableOpacity,
|
|
||||||
LayoutAnimation,
|
LayoutAnimation,
|
||||||
UIManager,
|
UIManager,
|
||||||
Platform,
|
Platform,
|
||||||
|
Alert,
|
||||||
} 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";
|
||||||
|
import * as keychain from "react-native-keychain";
|
||||||
|
import { BASE_URL } from "../../utils/ApiUrls";
|
||||||
|
|
||||||
// ✅ Enable LayoutAnimation for Android
|
// Enable LayoutAnimation for Android
|
||||||
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
const API_URL = "https://api.godemo.in/FundCollect/api/fund/getAll/my/groups";
|
|
||||||
const TOKEN =
|
|
||||||
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDAyOTkxLCJleHAiOjE3NTgwODkzOTF9.mMRPsULcvdfdCrJJBDjnT2okE3dJsTn42JBqi_vP0Xk";
|
|
||||||
|
|
||||||
// ✅ Each group card with expandable details
|
|
||||||
const GroupCard = ({ item }: { item: any }) => {
|
const GroupCard = ({ item }: { item: any }) => {
|
||||||
const [expanded, setExpanded] = useState(false);
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
const toggleExpand = () => {
|
const toggleExpand = () => {
|
||||||
LayoutAnimation.easeInEaseOut();
|
LayoutAnimation.easeInEaseOut();
|
||||||
setExpanded(!expanded);
|
setExpanded(!expanded);
|
||||||
};
|
};
|
||||||
|
|
||||||
const initial = item.groupName ? item.groupName.charAt(0).toUpperCase() : "?";
|
const initial = item.groupName ? item.groupName.charAt(0).toUpperCase() : "?";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.groupCard}>
|
<View style={styles.groupCard}>
|
||||||
<View style={styles.groupHeader}>
|
<View style={styles.groupLeft}>
|
||||||
<View style={styles.avatar}>
|
<View style={styles.groupAvatar}>
|
||||||
<Text style={styles.avatarText}>{initial}</Text>
|
<Text style={styles.avatarText}>{initial}</Text>
|
||||||
</View>
|
</View>
|
||||||
<View style={{ flex: 1 }}>
|
<View style={{ flex: 1 }}>
|
||||||
@@ -57,11 +53,11 @@ const GroupCard = ({ item }: { item: any }) => {
|
|||||||
</View>
|
</View>
|
||||||
|
|
||||||
{expanded && (
|
{expanded && (
|
||||||
<View style={styles.groupDetails}>
|
<View style={styles.detailsBox}>
|
||||||
{Object.entries(item).map(([key, value]) => (
|
{Object.entries(item).map(([key, value]) => (
|
||||||
<Text key={key} style={styles.detailText}>
|
<Text key={key} style={styles.detailText}>
|
||||||
<Text style={{ fontWeight: "600" }}>{key}:</Text>{" "}
|
<Text style={{ fontWeight: "600" }}>{key}:</Text>{" "}
|
||||||
{Array.isArray(value) ? value.join(", ") : String(value)}
|
{Array.isArray(value) ? value.join("-") : String(value)}
|
||||||
</Text>
|
</Text>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
@@ -70,7 +66,7 @@ const GroupCard = ({ item }: { item: any }) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
// ✅ Group list with search
|
// ✅ Group List with Search
|
||||||
const GroupList = () => {
|
const GroupList = () => {
|
||||||
const [groups, setGroups] = useState<any[]>([]);
|
const [groups, setGroups] = useState<any[]>([]);
|
||||||
const [filteredGroups, setFilteredGroups] = useState<any[]>([]);
|
const [filteredGroups, setFilteredGroups] = useState<any[]>([]);
|
||||||
@@ -79,10 +75,17 @@ const GroupList = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchGroups = async () => {
|
const fetchGroups = async () => {
|
||||||
|
const credentials = await keychain.getGenericPassword();
|
||||||
|
if (!credentials) {
|
||||||
|
Alert.alert("Error", "You are not logged in");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const token = credentials.password;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(API_URL, {
|
const res = await fetch(`${BASE_URL}fund/getAll/my/groups`, {
|
||||||
headers: { Accept: "*/*", Authorization: TOKEN },
|
headers: { Accept: "*/*", Authorization: `Bearer ${token}` },
|
||||||
});
|
});
|
||||||
|
console.log("my groups response", res);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
setGroups(data.data || []);
|
setGroups(data.data || []);
|
||||||
setFilteredGroups(data.data || []);
|
setFilteredGroups(data.data || []);
|
||||||
@@ -107,19 +110,10 @@ const GroupList = () => {
|
|||||||
}
|
}
|
||||||
}, [search, groups]);
|
}, [search, groups]);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) return <ActivityIndicator size="large" color={colors.Home} />;
|
||||||
return (
|
|
||||||
<ActivityIndicator
|
|
||||||
size="large"
|
|
||||||
color={colors.primary}
|
|
||||||
style={{ marginTop: 20 }}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={{ flex: 1 }}>
|
<>
|
||||||
{/* Search bar */}
|
|
||||||
<View style={styles.searchContainer}>
|
<View style={styles.searchContainer}>
|
||||||
<Ionicons name="search" size={20} color={colors.textLight} />
|
<Ionicons name="search" size={20} color={colors.textLight} />
|
||||||
<TextInput
|
<TextInput
|
||||||
@@ -131,7 +125,6 @@ const GroupList = () => {
|
|||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Group list */}
|
|
||||||
<FlatList
|
<FlatList
|
||||||
data={filteredGroups}
|
data={filteredGroups}
|
||||||
keyExtractor={(item) => item.id}
|
keyExtractor={(item) => item.id}
|
||||||
@@ -139,22 +132,21 @@ const GroupList = () => {
|
|||||||
ListEmptyComponent={
|
ListEmptyComponent={
|
||||||
<Text style={styles.emptyText}>No groups found</Text>
|
<Text style={styles.emptyText}>No groups found</Text>
|
||||||
}
|
}
|
||||||
contentContainerStyle={{ paddingBottom: 20 }}
|
|
||||||
/>
|
/>
|
||||||
</View>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GroupList;
|
export default GroupList;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
|
|
||||||
searchContainer: {
|
searchContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
backgroundColor: colors.white,
|
backgroundColor: colors.white,
|
||||||
borderRadius: 12,
|
borderRadius: 12,
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
margin: 10,
|
marginVertical: 10,
|
||||||
height: 45,
|
height: 45,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOpacity: 0.05,
|
shadowOpacity: 0.05,
|
||||||
@@ -167,40 +159,34 @@ const styles = StyleSheet.create({
|
|||||||
backgroundColor: colors.white,
|
backgroundColor: colors.white,
|
||||||
padding: 14,
|
padding: 14,
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
marginHorizontal: 10,
|
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
shadowColor: "#000",
|
shadowColor: "#000",
|
||||||
shadowOpacity: 0.05,
|
shadowOpacity: 0.05,
|
||||||
shadowRadius: 4,
|
shadowRadius: 4,
|
||||||
elevation: 2,
|
elevation: 2,
|
||||||
},
|
},
|
||||||
groupHeader: { flexDirection: "row", alignItems: "center" },
|
groupLeft: { flexDirection: "row", alignItems: "center" },
|
||||||
avatar: {
|
groupAvatar: {
|
||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: 25,
|
borderRadius: 25,
|
||||||
backgroundColor: colors.primary + "33",
|
backgroundColor: colors.pastelPink,
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
avatarText: { fontSize: 18, fontWeight: "700", color: colors.primary },
|
avatarText: { fontSize: 18, fontWeight: "700", color: colors.Home },
|
||||||
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
|
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
|
||||||
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
|
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
|
||||||
groupMeta: { fontSize: 12, color: colors.primary, marginTop: 2 },
|
groupMeta: { fontSize: 12, color: colors.Home, marginTop: 2 },
|
||||||
|
detailsBox: {
|
||||||
groupDetails: {
|
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
backgroundColor: "#f9f9f9",
|
backgroundColor: "#f9f9f9",
|
||||||
},
|
},
|
||||||
detailText: { fontSize: 12, color: colors.textDark, marginBottom: 4 },
|
detailText: { fontSize: 12, color: colors.textDark, marginBottom: 4 },
|
||||||
|
emptyText: { textAlign: "center", color: colors.textLight, marginTop: 20 },
|
||||||
emptyText: {
|
centered: { flex: 1, justifyContent: "center", alignItems: "center" },
|
||||||
textAlign: "center",
|
sectionTitle: { fontSize: 18, fontWeight: "600", color: colors.textDark },
|
||||||
color: colors.textLight,
|
});
|
||||||
marginTop: 20,
|
|
||||||
fontSize: 14,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
+38
-169
@@ -4,132 +4,11 @@ import {
|
|||||||
Text,
|
Text,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
TextInput,
|
|
||||||
FlatList,
|
|
||||||
ActivityIndicator,
|
|
||||||
LayoutAnimation,
|
|
||||||
UIManager,
|
|
||||||
Platform,
|
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
|
||||||
import colors from "../../components/colors";
|
import colors from "../../components/colors";
|
||||||
import AppView from "../../components/AppView";
|
import GroupList from "./GroupList";
|
||||||
|
import RequestList from "./RequestList";
|
||||||
|
|
||||||
// Enable LayoutAnimation for Android
|
|
||||||
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
|
||||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
const API_URL = "https://api.godemo.in/FundCollect/api/fund/getAll/my/groups";
|
|
||||||
const TOKEN =
|
|
||||||
"Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDAyOTkxLCJleHAiOjE3NTgwODkzOTF9.mMRPsULcvdfdCrJJBDjnT2okE3dJsTn42JBqi_vP0Xk";
|
|
||||||
|
|
||||||
const GroupCard = ({ item }: { item: any }) => {
|
|
||||||
const [expanded, setExpanded] = useState(false);
|
|
||||||
const toggleExpand = () => {
|
|
||||||
LayoutAnimation.easeInEaseOut();
|
|
||||||
setExpanded(!expanded);
|
|
||||||
};
|
|
||||||
const initial = item.groupName ? item.groupName.charAt(0).toUpperCase() : "?";
|
|
||||||
return (
|
|
||||||
<View style={styles.groupCard}>
|
|
||||||
<View style={styles.groupLeft}>
|
|
||||||
<View style={styles.groupAvatar}>
|
|
||||||
<Text style={styles.avatarText}>{initial}</Text>
|
|
||||||
</View>
|
|
||||||
<View style={{ flex: 1 }}>
|
|
||||||
<Text style={styles.groupName}>{item.groupName}</Text>
|
|
||||||
<Text style={styles.groupDescription}>{item.description}</Text>
|
|
||||||
<Text style={styles.groupMeta}>
|
|
||||||
{item.visibility} • {item.cycleType}
|
|
||||||
</Text>
|
|
||||||
</View>
|
|
||||||
<TouchableOpacity onPress={toggleExpand}>
|
|
||||||
<Ionicons
|
|
||||||
name={expanded ? "chevron-up-outline" : "chevron-forward-outline"}
|
|
||||||
size={22}
|
|
||||||
color={colors.textLight}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{expanded && (
|
|
||||||
<View style={styles.detailsBox}>
|
|
||||||
{Object.entries(item).map(([key, value]) => (
|
|
||||||
<Text key={key} style={styles.detailText}>
|
|
||||||
<Text style={{ fontWeight: "600" }}>{key}:</Text>{" "}
|
|
||||||
{Array.isArray(value) ? value.join("-") : String(value)}
|
|
||||||
</Text>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ Group List with Search
|
|
||||||
const GroupList = () => {
|
|
||||||
const [groups, setGroups] = useState<any[]>([]);
|
|
||||||
const [filteredGroups, setFilteredGroups] = useState<any[]>([]);
|
|
||||||
const [loading, setLoading] = useState(true);
|
|
||||||
const [search, setSearch] = useState("");
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchGroups = async () => {
|
|
||||||
try {
|
|
||||||
const res = await fetch(API_URL, {
|
|
||||||
headers: { Accept: "*/*", Authorization: TOKEN },
|
|
||||||
});
|
|
||||||
const data = await res.json();
|
|
||||||
setGroups(data.data || []);
|
|
||||||
setFilteredGroups(data.data || []);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("API error", e);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchGroups();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (search.trim() === "") {
|
|
||||||
setFilteredGroups(groups);
|
|
||||||
} else {
|
|
||||||
setFilteredGroups(
|
|
||||||
groups.filter((g) =>
|
|
||||||
g.groupName.toLowerCase().includes(search.toLowerCase())
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}, [search, groups]);
|
|
||||||
|
|
||||||
if (loading) return <ActivityIndicator size="large" color={colors.primary} />;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<View style={styles.searchContainer}>
|
|
||||||
<Ionicons name="search" size={20} color={colors.textLight} />
|
|
||||||
<TextInput
|
|
||||||
placeholder="Search groups..."
|
|
||||||
placeholderTextColor={colors.textLight}
|
|
||||||
style={styles.searchInput}
|
|
||||||
value={search}
|
|
||||||
onChangeText={setSearch}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
|
|
||||||
<FlatList
|
|
||||||
data={filteredGroups}
|
|
||||||
keyExtractor={(item) => item.id}
|
|
||||||
renderItem={({ item }) => <GroupCard item={item} />}
|
|
||||||
ListEmptyComponent={
|
|
||||||
<Text style={styles.emptyText}>No groups found</Text>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
// ✅ Tab Layout
|
// ✅ Tab Layout
|
||||||
const GroupsTabs = () => {
|
const GroupsTabs = () => {
|
||||||
@@ -139,71 +18,61 @@ const GroupsTabs = () => {
|
|||||||
switch (activeTab) {
|
switch (activeTab) {
|
||||||
case "MyGroups":
|
case "MyGroups":
|
||||||
return <GroupList />;
|
return <GroupList />;
|
||||||
case "Create":
|
case "Requests":
|
||||||
return (
|
return <RequestList />;
|
||||||
<View style={styles.centered}>
|
|
||||||
<Text style={styles.sectionTitle}>Create Group Screen</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
case "History":
|
|
||||||
return (
|
|
||||||
<View style={styles.centered}>
|
|
||||||
<Text style={styles.sectionTitle}>History Screen</Text>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppView barStyle="dark-content">
|
<>
|
||||||
<View style={styles.container}>
|
|
||||||
{/* Tabs */}
|
|
||||||
<View style={styles.tabs}>
|
|
||||||
{["MyGroups", "Create", "History"].map((tab) => (
|
|
||||||
<TouchableOpacity
|
|
||||||
key={tab}
|
|
||||||
style={[
|
|
||||||
styles.tabButton,
|
|
||||||
activeTab === tab && styles.activeTabButton,
|
|
||||||
]}
|
|
||||||
onPress={() => setActiveTab(tab)}
|
|
||||||
>
|
|
||||||
<Text
|
|
||||||
style={[
|
|
||||||
styles.tabText,
|
|
||||||
activeTab === tab && styles.activeTabText,
|
|
||||||
]}
|
|
||||||
>
|
|
||||||
{tab === "MyGroups" ? "My Groups" : tab}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
))}
|
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Content */}
|
{/* Tabs */}
|
||||||
{renderContent()}
|
<View style={styles.tabs}>
|
||||||
|
{["MyGroups", "Requests"].map((tab) => (
|
||||||
|
<TouchableOpacity
|
||||||
|
key={tab}
|
||||||
|
style={[
|
||||||
|
styles.tabButton,
|
||||||
|
activeTab === tab && styles.activeTabButton,
|
||||||
|
]}
|
||||||
|
onPress={() => setActiveTab(tab)}
|
||||||
|
>
|
||||||
|
<Text
|
||||||
|
style={[
|
||||||
|
styles.tabText,
|
||||||
|
activeTab === tab && styles.activeTabText,
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
{tab === "MyGroups" ? "My Groups" : tab}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
))}
|
||||||
</View>
|
</View>
|
||||||
</AppView>
|
|
||||||
|
{/* Content */}
|
||||||
|
{renderContent()}
|
||||||
|
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default GroupsTabs;
|
export default GroupsTabs;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: { },
|
|
||||||
tabs: {
|
tabs: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-around",
|
justifyContent: "space-around",
|
||||||
// marginVertical: 10,
|
marginVertical: 10,
|
||||||
borderBottomWidth: 1,
|
borderBottomWidth: 1,
|
||||||
borderBottomColor: "#ddd",
|
borderBottomColor: "#ddd",
|
||||||
},
|
},
|
||||||
tabButton: { paddingVertical: 10, flex: 1, alignItems: "center" },
|
tabButton: { paddingVertical: 10, flex: 1, alignItems: "center" },
|
||||||
tabText: { fontSize: 16, color: colors.textLight },
|
tabText: { fontSize: 16, color: colors.textLight },
|
||||||
activeTabButton: { borderBottomWidth: 2, borderBottomColor: colors.primary },
|
activeTabButton: { borderBottomWidth: 2, borderBottomColor: colors.Home },
|
||||||
activeTabText: { color: colors.primary, fontWeight: "600" },
|
activeTabText: { color: colors.Home, fontWeight: "600" },
|
||||||
|
|
||||||
searchContainer: {
|
searchContainer: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
@@ -235,15 +104,15 @@ const styles = StyleSheet.create({
|
|||||||
width: 50,
|
width: 50,
|
||||||
height: 50,
|
height: 50,
|
||||||
borderRadius: 25,
|
borderRadius: 25,
|
||||||
backgroundColor: colors.primary + "33",
|
backgroundColor: colors.Home + "33",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginRight: 12,
|
marginRight: 12,
|
||||||
},
|
},
|
||||||
avatarText: { fontSize: 18, fontWeight: "700", color: colors.primary },
|
avatarText: { fontSize: 18, fontWeight: "700", color: colors.Home },
|
||||||
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
|
groupName: { fontSize: 16, fontWeight: "600", color: colors.textDark },
|
||||||
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
|
groupDescription: { fontSize: 13, color: colors.textLight, marginTop: 2 },
|
||||||
groupMeta: { fontSize: 12, color: colors.primary, marginTop: 2 },
|
groupMeta: { fontSize: 12, color: colors.Home, marginTop: 2 },
|
||||||
detailsBox: {
|
detailsBox: {
|
||||||
marginTop: 10,
|
marginTop: 10,
|
||||||
padding: 10,
|
padding: 10,
|
||||||
|
|||||||
@@ -6,27 +6,40 @@ import {
|
|||||||
TouchableOpacity,
|
TouchableOpacity,
|
||||||
UIManager,
|
UIManager,
|
||||||
Platform,
|
Platform,
|
||||||
|
Image,
|
||||||
} from "react-native";
|
} from "react-native";
|
||||||
import AppView from "../../components/AppView";
|
import AppView from "../../components/AppView";
|
||||||
import UserProfileStatus from "./UserProfileStatus";
|
import UserProfileStatus from "./UserProfileStatus";
|
||||||
import colors from "../../components/colors";
|
import colors from "../../components/colors";
|
||||||
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
import { Ionicons } from "@react-native-vector-icons/ionicons";
|
||||||
import GroupsTabs from "./GroupsTabs";
|
import GroupsTabs from "./GroupsTabs";
|
||||||
|
import { useSelector } from "react-redux";
|
||||||
|
import { useNavigation } from "@react-navigation/native";
|
||||||
// Enable animation for Android
|
// Enable animation for Android
|
||||||
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
if (Platform.OS === "android" && UIManager.setLayoutAnimationEnabledExperimental) {
|
||||||
UIManager.setLayoutAnimationEnabledExperimental(true);
|
UIManager.setLayoutAnimationEnabledExperimental(true);
|
||||||
}
|
}
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
|
const user = useSelector((state: any) => state.user);
|
||||||
|
const navigation = useNavigation();
|
||||||
|
console.log("redux user data in Home:", user);
|
||||||
return (
|
return (
|
||||||
<AppView barStyle="dark-content">
|
<AppView barStyle="dark-content" style={styles.container}>
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.greeting}>Hello, Pooja 👋</Text>
|
<Text style={styles.greeting}>Hello, {user.firstName} {user.lastName}👋</Text>
|
||||||
<TouchableOpacity>
|
<TouchableOpacity onPress={() => navigation.navigate("Profile" as never)}>
|
||||||
<Ionicons
|
{/* <Ionicons
|
||||||
name="person-circle-outline"
|
name="person-circle-outline"
|
||||||
size={40}
|
size={40}
|
||||||
color={colors.primary}
|
color={colors.primary}
|
||||||
|
/> */}
|
||||||
|
<Image
|
||||||
|
source={
|
||||||
|
user.imageBlob
|
||||||
|
&& { uri: user.imageBlob ? `data:image/jpeg;base64,${user.imageBlob}` : user.profilePicture }
|
||||||
|
}
|
||||||
|
style={{ width: 40, height: 40, borderRadius: 20, borderColor: colors.primary, borderWidth: 2 }}
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
@@ -40,14 +53,16 @@ const Home = () => {
|
|||||||
export default Home;
|
export default Home;
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
container: {},
|
container: {
|
||||||
|
paddingBottom: '25%',
|
||||||
|
},
|
||||||
header: {
|
header: {
|
||||||
flexDirection: "row",
|
flexDirection: "row",
|
||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
marginBottom: 12,
|
marginBottom: 12,
|
||||||
},
|
},
|
||||||
greeting: { fontSize: 22, fontWeight: "600", color: colors.textDark },
|
greeting: { fontSize: 22, fontWeight: "600", color: colors.Home },
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,606 @@
|
|||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import {
|
||||||
|
View,
|
||||||
|
Text,
|
||||||
|
StyleSheet,
|
||||||
|
ScrollView,
|
||||||
|
TouchableOpacity,
|
||||||
|
RefreshControl,
|
||||||
|
Alert,
|
||||||
|
ActivityIndicator,
|
||||||
|
TextInput
|
||||||
|
} from 'react-native';
|
||||||
|
import { Ionicons } from '@react-native-vector-icons/ionicons';
|
||||||
|
import colors from '../../components/colors';
|
||||||
|
import DeclineModal from './DeclineModal';
|
||||||
|
|
||||||
|
// Define TypeScript interfaces
|
||||||
|
interface GroupRequest {
|
||||||
|
requestId: string;
|
||||||
|
groupName: string;
|
||||||
|
groupId: string;
|
||||||
|
userId: number;
|
||||||
|
userName: string;
|
||||||
|
mobile: string;
|
||||||
|
status: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ApiResponse {
|
||||||
|
message: string;
|
||||||
|
status: string;
|
||||||
|
statusCode: number;
|
||||||
|
data: GroupRequest[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const RequestList: React.FC = () => {
|
||||||
|
const [requests, setRequests] = useState<GroupRequest[]>([]);
|
||||||
|
const [filteredRequests, setFilteredRequests] = useState<GroupRequest[]>([]);
|
||||||
|
const [refreshing, setRefreshing] = useState<boolean>(false);
|
||||||
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [searchQuery, setSearchQuery] = useState<string>('');
|
||||||
|
const [processingRequest, setProcessingRequest] = useState<string | null>(null);
|
||||||
|
const [showDeclineModal, setShowDeclineModal] = useState<{modal: boolean, requestId: string | null}>({modal: false, requestId: null});
|
||||||
|
const [selectedRequestId, setSelectedRequestId] = useState<string | null>(null);
|
||||||
|
|
||||||
|
// Fetch data from API
|
||||||
|
const fetchRequests = async (): Promise<void> => {
|
||||||
|
try {
|
||||||
|
setRefreshing(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
"https://api.godemo.in/FundCollect/api/fund/group/pending/request",
|
||||||
|
{
|
||||||
|
method: "GET",
|
||||||
|
headers: {
|
||||||
|
accept: "*/*",
|
||||||
|
Authorization: "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data: ApiResponse = await response.json();
|
||||||
|
|
||||||
|
if (data.status === "success") {
|
||||||
|
setRequests(data.data);
|
||||||
|
setFilteredRequests(data.data);
|
||||||
|
} else {
|
||||||
|
throw new Error("Failed to fetch requests");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
|
||||||
|
setError(errorMessage);
|
||||||
|
console.error("API Error:", error);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
setRefreshing(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchRequests();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
// Filter requests based on search query
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchQuery.trim() === '') {
|
||||||
|
setFilteredRequests(requests);
|
||||||
|
} else {
|
||||||
|
const query = searchQuery.toLowerCase();
|
||||||
|
const filtered = requests.filter(request =>
|
||||||
|
request.groupName.toLowerCase().includes(query) ||
|
||||||
|
request.userName.toLowerCase().includes(query) ||
|
||||||
|
request.mobile.includes(query) ||
|
||||||
|
request.status.toLowerCase().includes(query)
|
||||||
|
);
|
||||||
|
setFilteredRequests(filtered);
|
||||||
|
}
|
||||||
|
}, [searchQuery, requests]);
|
||||||
|
|
||||||
|
const handleAccept = async (requestId: string): Promise<void> => {
|
||||||
|
Alert.alert(
|
||||||
|
"Accept Request",
|
||||||
|
"Are you sure you want to accept this request?",
|
||||||
|
[
|
||||||
|
{ text: "Cancel", style: "cancel" },
|
||||||
|
{
|
||||||
|
text: "Accept",
|
||||||
|
onPress: async () => {
|
||||||
|
try {
|
||||||
|
setProcessingRequest(requestId);
|
||||||
|
|
||||||
|
// Make API call to approve the request
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.godemo.in/FundCollect/api/fund/requests/${requestId}/approve`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"accept": "*/*",
|
||||||
|
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === "success") {
|
||||||
|
// Remove the accepted request from the list
|
||||||
|
const updatedRequests = requests.filter(req => req.requestId !== requestId);
|
||||||
|
setRequests(updatedRequests);
|
||||||
|
Alert.alert("Success", data.message || "Request accepted successfully");
|
||||||
|
fetchRequests();
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || "Failed to accept request");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
|
||||||
|
Alert.alert("Error", `Failed to accept request: ${errorMessage}`);
|
||||||
|
console.error("Accept Error:", error);
|
||||||
|
} finally {
|
||||||
|
setProcessingRequest(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDeclineConfirm = async (requestedId: string | null, reason: string) => {
|
||||||
|
setShowDeclineModal({modal: false, requestId: null});
|
||||||
|
try {
|
||||||
|
setProcessingRequest(requestedId);
|
||||||
|
|
||||||
|
// Make API call to decline the request
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.godemo.in/FundCollect/api/fund/requests/${requestedId}/reject?reason=/${reason}`,
|
||||||
|
{
|
||||||
|
method: "PUT",
|
||||||
|
headers: {
|
||||||
|
"accept": "*/*",
|
||||||
|
"Authorization": "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI2MjY1MjI5MzcxIiwidXNlcklkIjoiMTAwMDAxIiwiaWF0IjoxNzU4MDkxMjEzLCJleHAiOjE3NTgxNzc2MTN9.T3BvU7nl8fnjwwVobIWRmSFFxQzOjtyIct1Ba0i9lIU"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse response
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
if (data.status === "success") {
|
||||||
|
// Remove the declined request from the list
|
||||||
|
const updatedRequests = requests.filter(req => req.requestId !== selectedRequestId);
|
||||||
|
setRequests(updatedRequests);
|
||||||
|
Alert.alert("Success", data.message || "Request declined successfully");
|
||||||
|
fetchRequests();
|
||||||
|
} else {
|
||||||
|
throw new Error(data.message || "Failed to decline request");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "An unknown error occurred";
|
||||||
|
Alert.alert("Error", `Failed to decline request: ${errorMessage}`);
|
||||||
|
console.error("Decline Error:", error);
|
||||||
|
} finally {
|
||||||
|
setProcessingRequest(null);
|
||||||
|
}// call your API
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
const onRefresh = (): void => {
|
||||||
|
fetchRequests();
|
||||||
|
};
|
||||||
|
|
||||||
|
const getInitials = (name: string): string => {
|
||||||
|
return name
|
||||||
|
.split(' ')
|
||||||
|
.map(word => word.charAt(0))
|
||||||
|
.join('')
|
||||||
|
.toUpperCase()
|
||||||
|
.substring(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSearch = (): void => {
|
||||||
|
setSearchQuery('');
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<View style={styles.centerContainer}>
|
||||||
|
<ActivityIndicator size="large" color="#3498db" />
|
||||||
|
<Text style={styles.loadingText}>Loading requests...</Text>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<View style={styles.centerContainer}>
|
||||||
|
<Ionicons name="alert-circle" size={64} color="#e74c3c" />
|
||||||
|
<Text style={styles.errorText}>Error loading requests</Text>
|
||||||
|
<Text style={styles.errorSubText}>{error}</Text>
|
||||||
|
<TouchableOpacity style={styles.retryButton} onPress={fetchRequests}>
|
||||||
|
<Text style={styles.retryButtonText}>Try Again</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={styles.container}>
|
||||||
|
<View style={styles.header}>
|
||||||
|
<Text style={styles.headerTitle}>Pending Requests</Text>
|
||||||
|
<Text style={styles.headerSubtitle}>
|
||||||
|
{filteredRequests.length} pending group request{filteredRequests.length !== 1 ? 's' : ''}
|
||||||
|
{searchQuery ? ` matching "${searchQuery}"` : ''}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Search Bar */}
|
||||||
|
<View style={styles.searchContainer}>
|
||||||
|
<Ionicons name="search" size={20} color="#95a5a6" style={styles.searchIcon} />
|
||||||
|
<TextInput
|
||||||
|
style={styles.searchInput}
|
||||||
|
placeholder="Search by group, name, phone or status..."
|
||||||
|
value={searchQuery}
|
||||||
|
onChangeText={setSearchQuery}
|
||||||
|
placeholderTextColor="#95a5a6"
|
||||||
|
/>
|
||||||
|
{searchQuery ? (
|
||||||
|
<TouchableOpacity onPress={clearSearch} style={styles.clearButton}>
|
||||||
|
<Ionicons name="close-circle" size={20} color="#95a5a6" />
|
||||||
|
</TouchableOpacity>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
refreshControl={
|
||||||
|
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
|
||||||
|
}
|
||||||
|
contentContainerStyle={filteredRequests.length === 0 ? styles.emptyScrollView : undefined}
|
||||||
|
>
|
||||||
|
{filteredRequests.length === 0 ? (
|
||||||
|
<View style={styles.emptyState}>
|
||||||
|
{searchQuery ? (
|
||||||
|
<>
|
||||||
|
<Ionicons name="search" size={64} color="#ccc" />
|
||||||
|
<Text style={styles.emptyStateText}>No matching requests</Text>
|
||||||
|
<Text style={styles.emptyStateSubtext}>
|
||||||
|
No requests found for "{searchQuery}"
|
||||||
|
</Text>
|
||||||
|
<TouchableOpacity onPress={clearSearch} style={styles.clearSearchButton}>
|
||||||
|
<Text style={styles.clearSearchText}>Clear Search</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Ionicons name="checkmark-done-circle" size={64} color="#ccc" />
|
||||||
|
<Text style={styles.emptyStateText}>No pending requests</Text>
|
||||||
|
<Text style={styles.emptyStateSubtext}>
|
||||||
|
You're all caught up! There are no pending group requests.
|
||||||
|
</Text>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
filteredRequests.map((request) => (
|
||||||
|
<View key={request.requestId} style={styles.requestCard}>
|
||||||
|
<View style={styles.cardHeader}>
|
||||||
|
<View style={styles.groupInfo}>
|
||||||
|
<View style={styles.avatar}>
|
||||||
|
<Text style={styles.avatarText}>
|
||||||
|
{getInitials(request.groupName)}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
<View>
|
||||||
|
<Text style={styles.groupName}>{request.groupName}</Text>
|
||||||
|
<Text style={styles.requestText}>Group Join Request</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<View style={[
|
||||||
|
styles.statusBadge,
|
||||||
|
request.status === 'PENDING' ? styles.pendingBadge : styles.otherBadge
|
||||||
|
]}>
|
||||||
|
<Text style={styles.statusText}>{request.status}</Text>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.divider} />
|
||||||
|
|
||||||
|
<View style={styles.userInfo}>
|
||||||
|
<Ionicons name="person" size={16} color="#666" />
|
||||||
|
<Text style={styles.userName}>{request.userName}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.userInfo}>
|
||||||
|
<Ionicons name="call" size={16} color="#666" />
|
||||||
|
<Text style={styles.userDetail}>{request.mobile}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.actions}>
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.declineButton]}
|
||||||
|
onPress={() => setShowDeclineModal({modal:true, requestId: request.requestId})}
|
||||||
|
disabled={processingRequest === request.requestId}
|
||||||
|
>
|
||||||
|
{processingRequest === request.requestId ? (
|
||||||
|
<ActivityIndicator size="small" color="#e74c3c" />
|
||||||
|
) : (
|
||||||
|
<Text style={styles.declineButtonText}>Decline</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
|
<TouchableOpacity
|
||||||
|
style={[styles.button, styles.acceptButton]}
|
||||||
|
onPress={() => handleAccept(request.requestId)}
|
||||||
|
disabled={processingRequest === request.requestId}
|
||||||
|
>
|
||||||
|
{processingRequest === request.requestId ? (
|
||||||
|
<ActivityIndicator size="small" color="#fff" />
|
||||||
|
) : (
|
||||||
|
<Text style={styles.acceptButtonText}>Accept</Text>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
{showDeclineModal.modal && (
|
||||||
|
<DeclineModal
|
||||||
|
visible={showDeclineModal.modal}
|
||||||
|
onClose={() => setShowDeclineModal({modal: false, requestId: null})}
|
||||||
|
onSubmit={(reason) => handleDeclineConfirm(showDeclineModal.requestId, reason)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
container: {
|
||||||
|
paddingBottom: '100%',
|
||||||
|
},
|
||||||
|
centerContainer: {
|
||||||
|
flex: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#f5f7fb',
|
||||||
|
padding: 20,
|
||||||
|
},
|
||||||
|
loadingText: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#666',
|
||||||
|
marginTop: 12,
|
||||||
|
},
|
||||||
|
errorText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#e74c3c',
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
errorSubText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#7f8c8d',
|
||||||
|
textAlign: 'center',
|
||||||
|
marginBottom: 20,
|
||||||
|
},
|
||||||
|
retryButton: {
|
||||||
|
backgroundColor: colors.pastelPink,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
paddingVertical: 10,
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
retryButtonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
header: {
|
||||||
|
// backgroundColor: '#fff',
|
||||||
|
// // padding: 20,
|
||||||
|
// borderBottomWidth: 1,
|
||||||
|
// borderBottomColor: '#eee',
|
||||||
|
// shadowColor: '#000',
|
||||||
|
// shadowOffset: { width: 0, height: 2 },
|
||||||
|
// shadowOpacity: 0.1,
|
||||||
|
// shadowRadius: 3,
|
||||||
|
// elevation: 3,
|
||||||
|
},
|
||||||
|
headerTitle: {
|
||||||
|
fontSize: 24,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
color: '#2c3e50',
|
||||||
|
marginBottom: 5,
|
||||||
|
},
|
||||||
|
headerSubtitle: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#7f8c8d',
|
||||||
|
},
|
||||||
|
// Search styles
|
||||||
|
searchContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
marginVertical: 16,
|
||||||
|
marginBottom: 8,
|
||||||
|
borderRadius: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
height: 50,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 1 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 2,
|
||||||
|
elevation: 2,
|
||||||
|
},
|
||||||
|
searchIcon: {
|
||||||
|
marginRight: 8,
|
||||||
|
},
|
||||||
|
searchInput: {
|
||||||
|
flex: 1,
|
||||||
|
height: '100%',
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#2c3e50',
|
||||||
|
padding: 0,
|
||||||
|
},
|
||||||
|
clearButton: {
|
||||||
|
padding: 4,
|
||||||
|
},
|
||||||
|
clearSearchButton: {
|
||||||
|
marginTop: 16,
|
||||||
|
paddingVertical: 8,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
backgroundColor: '#ecf0f1',
|
||||||
|
borderRadius: 6,
|
||||||
|
},
|
||||||
|
clearSearchText: {
|
||||||
|
color: '#7f8c8d',
|
||||||
|
fontWeight: '500',
|
||||||
|
},
|
||||||
|
emptyScrollView: {
|
||||||
|
flexGrow: 1,
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
requestCard: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderRadius: 12,
|
||||||
|
marginBottom: 16,
|
||||||
|
padding: 16,
|
||||||
|
shadowColor: '#000',
|
||||||
|
shadowOffset: { width: 0, height: 2 },
|
||||||
|
shadowOpacity: 0.1,
|
||||||
|
shadowRadius: 4,
|
||||||
|
elevation: 3,
|
||||||
|
},
|
||||||
|
cardHeader: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 12,
|
||||||
|
},
|
||||||
|
groupInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
borderRadius: 25,
|
||||||
|
backgroundColor: colors.pastelPink,
|
||||||
|
justifyContent: 'center',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginRight: 12,
|
||||||
|
},
|
||||||
|
avatarText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 'bold',
|
||||||
|
},
|
||||||
|
groupName: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#2c3e50',
|
||||||
|
},
|
||||||
|
requestText: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#7f8c8d',
|
||||||
|
marginTop: 2,
|
||||||
|
},
|
||||||
|
statusBadge: {
|
||||||
|
paddingHorizontal: 10,
|
||||||
|
paddingVertical: 5,
|
||||||
|
borderRadius: 12,
|
||||||
|
},
|
||||||
|
pendingBadge: {
|
||||||
|
backgroundColor: '#fff4e6',
|
||||||
|
},
|
||||||
|
otherBadge: {
|
||||||
|
backgroundColor: '#e8f5e9',
|
||||||
|
},
|
||||||
|
statusText: {
|
||||||
|
fontSize: 12,
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
divider: {
|
||||||
|
height: 1,
|
||||||
|
backgroundColor: '#ecf0f1',
|
||||||
|
marginVertical: 12,
|
||||||
|
},
|
||||||
|
userInfo: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
userName: {
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#2c3e50',
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
userDetail: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#7f8c8d',
|
||||||
|
marginLeft: 8,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginTop: 16,
|
||||||
|
},
|
||||||
|
button: {
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 20,
|
||||||
|
borderRadius: 8,
|
||||||
|
marginLeft: 12,
|
||||||
|
minWidth: 100,
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
acceptButton: {
|
||||||
|
backgroundColor: '#2ecc71',
|
||||||
|
},
|
||||||
|
declineButton: {
|
||||||
|
backgroundColor: '#fff',
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e74c3c',
|
||||||
|
},
|
||||||
|
acceptButtonText: {
|
||||||
|
color: '#fff',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
declineButtonText: {
|
||||||
|
color: '#e74c3c',
|
||||||
|
fontWeight: '600',
|
||||||
|
},
|
||||||
|
emptyState: {
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
padding: 40,
|
||||||
|
},
|
||||||
|
emptyStateText: {
|
||||||
|
fontSize: 18,
|
||||||
|
fontWeight: '600',
|
||||||
|
color: '#7f8c8d',
|
||||||
|
marginTop: 16,
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
emptyStateSubtext: {
|
||||||
|
fontSize: 14,
|
||||||
|
color: '#bdc3c7',
|
||||||
|
textAlign: 'center',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default RequestList;
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import React, { useState, useEffect, useCallback } from "react";
|
import React, { useState, useEffect, useCallback, use } from "react";
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
View,
|
View,
|
||||||
@@ -22,7 +22,7 @@ import SwitchNavigator from "./SwitchNavigator";
|
|||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { ApiResponse } from "../home/UserProfileStatus";
|
import { ApiResponse } from "../home/UserProfileStatus";
|
||||||
import { BASE_URL } from "../../utils/ApiUrls";
|
import { BASE_URL } from "../../utils/ApiUrls";
|
||||||
|
import AppView from "../../components/AppView";
|
||||||
|
|
||||||
|
|
||||||
type UserProfile = {
|
type UserProfile = {
|
||||||
@@ -241,38 +241,6 @@ const Profile = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
const handleLogout = (navigation: any) => {
|
|
||||||
Alert.alert(
|
|
||||||
"Logout",
|
|
||||||
"Are you sure you want to logout?",
|
|
||||||
[
|
|
||||||
{
|
|
||||||
text: "Cancel",
|
|
||||||
style: "cancel",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
text: "Yes, Logout",
|
|
||||||
style: "destructive",
|
|
||||||
onPress: async () => {
|
|
||||||
try {
|
|
||||||
// Clear saved credentials from Keychain
|
|
||||||
await Keychain.resetGenericPassword();
|
|
||||||
|
|
||||||
// Navigate to login screen
|
|
||||||
navigation.reset({
|
|
||||||
index: 0,
|
|
||||||
routes: [{ name: "Login" }],
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Logout error:", error);
|
|
||||||
Alert.alert("Error", "Failed to logout. Please try again.");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
{ cancelable: true }
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
@@ -280,8 +248,6 @@ const Profile = () => {
|
|||||||
}, [])
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
console.log("login user data :", user);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetchUserProfile();
|
fetchUserProfile();
|
||||||
}, []);
|
}, []);
|
||||||
@@ -308,9 +274,7 @@ const Profile = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<AppView>
|
||||||
<StatusBar barStyle="light-content" />
|
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<Text style={styles.headerTitle}>User Profile</Text>
|
<Text style={styles.headerTitle}>User Profile</Text>
|
||||||
@@ -507,7 +471,7 @@ const Profile = () => {
|
|||||||
</View>
|
</View>
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
</View>
|
</AppView>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -533,7 +497,7 @@ const styles = StyleSheet.create({
|
|||||||
justifyContent: "space-between",
|
justifyContent: "space-between",
|
||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
padding: 16,
|
padding: 16,
|
||||||
paddingTop: 50,
|
// paddingTop: 50,
|
||||||
// backgroundColor: "#6366f1",
|
// backgroundColor: "#6366f1",
|
||||||
borderBottomLeftRadius: 20,
|
borderBottomLeftRadius: 20,
|
||||||
borderBottomRightRadius: 20,
|
borderBottomRightRadius: 20,
|
||||||
|
|||||||
Reference in New Issue
Block a user