From 070f7ace1667d019c90111955d8cc133aba02cfe Mon Sep 17 00:00:00 2001 From: Jaanu423 Date: Mon, 15 Sep 2025 17:05:52 +0530 Subject: [PATCH] create orgnizer screen and intrigate api --- src/components/colors.ts | 16 ++ src/components/common/Custom.tsx | 175 +++++++----------- src/navigation/AppNavigator.tsx | 24 +-- src/navigation/BottomTabs.tsx | 16 +- src/screens/OtpVarificationScreen.tsx | 3 +- src/screens/home/Home.tsx | 16 ++ src/screens/home/UserProfileStatus.tsx | 226 ++++++++++++++++++++++++ src/screens/profile/BecomeOrg.tsx | 143 +++++++++++++++ src/screens/profile/Profile.tsx | 67 ++++++- src/screens/profile/SwitchNavigator.tsx | 67 +++++++ 10 files changed, 613 insertions(+), 140 deletions(-) create mode 100644 src/components/colors.ts create mode 100644 src/screens/home/Home.tsx create mode 100644 src/screens/home/UserProfileStatus.tsx create mode 100644 src/screens/profile/BecomeOrg.tsx create mode 100644 src/screens/profile/SwitchNavigator.tsx diff --git a/src/components/colors.ts b/src/components/colors.ts new file mode 100644 index 0000000..6da0717 --- /dev/null +++ b/src/components/colors.ts @@ -0,0 +1,16 @@ +// colors.ts +const colors = { + pastelPink: "#F8BBD0", // darker pink + pastelBlue: "#90CAF9", // darker blue + pastelGreen: "#A5D6A7", // darker green + pastelOrange: "#FFCC80", // darker orange + + // common text + UI colors + textDark: "#212121", + textLight: "#555555", + white: "#FFFFFF", + black: "#000000", + primary: "#007AFF", +}; + +export default colors; diff --git a/src/components/common/Custom.tsx b/src/components/common/Custom.tsx index e5ae7e9..b1d17f1 100644 --- a/src/components/common/Custom.tsx +++ b/src/components/common/Custom.tsx @@ -5,48 +5,46 @@ import { Text, TextInput, TouchableOpacity, + TextInputProps, } from "react-native"; -import { TextInputProps } from "react-native"; -import {Ionicons} from "@react-native-vector-icons/ionicons"; +import {Ionicons, IoniconsIconName} from "@react-native-vector-icons/ionicons"; +import colors from "../colors"; +// --- Custom Input --- type CustomInputProps = TextInputProps & { - label: string; - value: string; - placeholder: string; - onChangeText: (text: string) => void; - icon?: string; + label?: string; + icon?: IoniconsIconName; }; const CustomInput: React.FC = ({ label, - value, - placeholder, - onChangeText, icon, + style, ...rest -}) => ( - - {label} - - {icon && } - +}) => { + return ( + + {label ? {label} : null} + + {icon && } + + - -); + ); +}; +// --- Custom Button --- type CustomButtonProps = { title: string; onPress: () => void; - type?: "primary" | "secondary" | "danger" | "outline"; - icon?: string | null; + type?: "primary" | "secondary" | "danger"; + icon?: IoniconsIconName; style?: object; + buttonBackground?: string; }; const CustomButton: React.FC = ({ @@ -54,77 +52,37 @@ const CustomButton: React.FC = ({ onPress, type = "primary", icon, -}) => ( - - - {icon && } - - {title} - - - -); + style = {}, + buttonBackground, +}) => { + const backgroundColor = + buttonBackground ?? + (type === "secondary" + ? colors.pastelBlue + : type === "danger" + ? colors.pastelOrange + : colors.primary); + const textColor = type === "secondary" ? colors.textDark : colors.white; + + return ( + + + {icon && } + {title} + + + ); +}; + +// --- Styles --- const styles = StyleSheet.create({ - container: { flex: 1, backgroundColor: "#f5f7fb" }, - loadingContainer: { flex: 1, justifyContent: "center", alignItems: "center" }, - loadingText: { marginTop: 10, fontSize: 16, color: "#007AFF" }, - header: { - flexDirection: "row", - justifyContent: "space-between", - padding: 16, - paddingTop: 40, - backgroundColor: "#fff", - alignItems: "center", - borderBottomWidth: 1, - borderColor: "#eee", - }, - headerTitle: { fontSize: 20, fontWeight: "600", color: "#333" }, - profileCard: { - borderRadius: 16, - margin: 16, - backgroundColor: "#fff", - padding: 20, - shadowColor: "#000", - shadowOpacity: 0.1, - shadowRadius: 6, - elevation: 4, - }, - profileHeader: { alignItems: "center", marginBottom: 16 }, - avatar: { width: 100, height: 100, borderRadius: 50, marginBottom: 8 }, - cameraIcon: { - position: "absolute", - right: 110, - bottom: 10, - backgroundColor: "#007AFF", - borderRadius: 15, - width: 32, - height: 32, - justifyContent: "center", - alignItems: "center", - }, - detail: { - fontSize: 16, - marginBottom: 10, - color: "#444", - paddingLeft: 4, - }, inputContainer: { marginBottom: 12 }, - inputLabel: { fontSize: 14, fontWeight: "500", color: "#555", marginBottom: 6 }, + inputLabel: { fontSize: 14, fontWeight: "500", color: colors.textDark, marginBottom: 6 }, inputWrapper: { flexDirection: "row", alignItems: "center", @@ -138,38 +96,29 @@ const styles = StyleSheet.create({ input: { flex: 1, borderWidth: 1, - borderColor: "#ddd", + borderColor: colors.textLight, borderRadius: 10, padding: 12, paddingLeft: 12, - backgroundColor: "#fdfdfd", - color: "#000", + backgroundColor: colors.white, + color: colors.textDark, fontSize: 15, }, button: { - backgroundColor: "#007AFF", paddingVertical: 12, borderRadius: 12, alignItems: "center", marginVertical: 6, - shadowColor: "#007AFF", - shadowOpacity: 0.2, - shadowRadius: 4, - elevation: 3, - }, - buttonSecondary: { - backgroundColor: "#e5e5ea", - }, - buttonDanger: { - backgroundColor: "#ff3b30", }, buttonContent: { flexDirection: "row", alignItems: "center", + justifyContent: "center", + }, + buttonText: { + fontSize: 16, + fontWeight: "600", }, - buttonText: { color: "#fff", fontSize: 16, fontWeight: "600" }, - buttonTextSecondary: { color: "#333" }, - buttonTextDanger: { color: "#fff" }, }); -export { CustomInput, styles, CustomButton }; +export { CustomInput, CustomButton, styles }; diff --git a/src/navigation/AppNavigator.tsx b/src/navigation/AppNavigator.tsx index a762f87..f210654 100644 --- a/src/navigation/AppNavigator.tsx +++ b/src/navigation/AppNavigator.tsx @@ -10,6 +10,7 @@ import OrganizerRegisterScreen from "../screens/OrgnizerRagister"; import GroupCreation from "../screens/GroupCreation"; import GroupReview from "../screens/GroupReview"; import BottomTabs from "./BottomTabs"; +import BecomeOrg from "../screens/profile/BecomeOrg"; const Stack = createNativeStackNavigator(); export type RootStackParamList = { @@ -18,7 +19,7 @@ export type RootStackParamList = { Home: undefined HomeTabs: undefined splash: undefined - OrganizerRegister:undefined + OrganizerRegister: undefined Profile: undefined; GroupCreation: undefined; GroupReview: undefined; @@ -40,13 +41,13 @@ function HomeScreen() { export default function AppNavigator() { const linking = { - prefixes: ["myapp://", "https://api.godemo.in"], // supports both - config: { - screens: { - Profile: "EProcurementSB", // dynamic route + prefixes: ["myapp://", "https://api.godemo.in"], // supports both + config: { + screens: { + Profile: "EProcurementSB", // dynamic route + }, }, - }, -}; + }; return ( @@ -54,10 +55,11 @@ export default function AppNavigator() { - - - - + + + + + ); diff --git a/src/navigation/BottomTabs.tsx b/src/navigation/BottomTabs.tsx index c828df2..d66a3e8 100644 --- a/src/navigation/BottomTabs.tsx +++ b/src/navigation/BottomTabs.tsx @@ -4,6 +4,7 @@ import { View, Text, StyleSheet, Platform } from "react-native"; import { Ionicons } from '@react-native-vector-icons/ionicons'; import GroupScreen from "../screens/GroupScreen"; import Profile from "../screens/profile/Profile"; +import Home from "../screens/home/Home"; const Tab = createBottomTabNavigator(); @@ -17,14 +18,7 @@ const TAB_CONFIG = { labelWeight: "600", }; -// Dummy Screens -function HomeScreen() { - return ( - - 🏠 Home - - ); -} + function SettingsScreen() { return ( @@ -70,9 +64,9 @@ export default function BottomTabs() { }, })} > - + - + ); @@ -104,7 +98,7 @@ const styles = StyleSheet.create({ shadowOffset: { width: 0, height: -2 }, shadowRadius: 8, elevation: 20, - paddingTop:10, + paddingTop: 10, borderTopWidth: 0, }, }); diff --git a/src/screens/OtpVarificationScreen.tsx b/src/screens/OtpVarificationScreen.tsx index 994433b..968dfdf 100644 --- a/src/screens/OtpVarificationScreen.tsx +++ b/src/screens/OtpVarificationScreen.tsx @@ -86,7 +86,8 @@ export default function OtpVerificationScreen() { if (res.data.statusCode === 200) { saveToken(res.data.data); navigation.navigate('Home') - ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT);} + ToastAndroid.show("OTP Verified Successfully!", ToastAndroid.SHORT); + } } catch (err: any) { console.error("❌ OTP Verification Error:", err?.response?.data || err.message); ToastAndroid.show(err.message || "Invalid OTP. Please try again.", ToastAndroid.SHORT); diff --git a/src/screens/home/Home.tsx b/src/screens/home/Home.tsx new file mode 100644 index 0000000..fc94e21 --- /dev/null +++ b/src/screens/home/Home.tsx @@ -0,0 +1,16 @@ +import { StyleSheet, Text, View } from 'react-native' +import React from 'react' +import AppView from '../../components/AppView' +import UserProfileStatus from './UserProfileStatus' + +const Home = () => { + return ( + + + + ) +} + +export default Home + +const styles = StyleSheet.create({}) \ No newline at end of file diff --git a/src/screens/home/UserProfileStatus.tsx b/src/screens/home/UserProfileStatus.tsx new file mode 100644 index 0000000..334030b --- /dev/null +++ b/src/screens/home/UserProfileStatus.tsx @@ -0,0 +1,226 @@ +import React, { use, useCallback, useEffect, useState } from "react"; +import { + StyleSheet, + Text, + View, + ActivityIndicator, + TouchableOpacity, + Modal, + Alert, +} from "react-native"; +import * as Keychain from "react-native-keychain"; +import { base_URL } from "../../utils/ApiUrls"; +import { useFocusEffect, useNavigation } from "@react-navigation/native"; + +export type ApiResponse = { + message: string; + status: string; + statusCode: number; + data: { + role: string; + organizerStatus: string; + profileStatus: boolean; + }; +}; + + +const UserProfileStatus: React.FC = () => { + const navigation = useNavigation(); + const [loading, setLoading] = useState(true); + const [statusData, setStatusData] = useState(null); + const [error, setError] = useState(null); + const [showModal, setShowModal] = useState(false); + const [intervalId, setIntervalId] = useState(null); + + const userStatus = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + setLoading(true); + setError(null); + const response = await fetch(`${base_URL}user/profile/status`, { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + }); + + const json: ApiResponse = await response.json(); + + console.log("👤 Profile Status Response:", json); + if (json.status === "success") { + setStatusData(json.data); + + if (json.data.profileStatus === false) { + setShowModal(true); + } + } else { + setError(json.message || "Failed to fetch status"); + } + } catch (err) { + setError("Something went wrong. Please try again."); + } finally { + setLoading(false); + } + }; + + useFocusEffect( + useCallback(() => { + userStatus(); + }, []) + ); + + useEffect(() => { + if (statusData && statusData.profileStatus === false) { + const id = setInterval(() => { + setShowModal(true); + }, 5000); + setIntervalId(id); + return () => clearInterval(id); + } + }, [statusData]); + + const handleGoToProfile = () => { + // Stop modal repetition + if (intervalId) { + clearInterval(intervalId); + setIntervalId(null); + } + + setShowModal(false); + + // Option 1: Mark profile as completed temporarily (so condition stops) + setStatusData(prev => (prev ? { ...prev, profileStatus: true } : prev)); + + // Navigate to Profile screen + navigation.navigate("Profile" as never); + }; + + return ( + + Welcome Back 👋 + + + Profile Status + Role: {statusData?.role} + Organizer: {statusData?.organizerStatus} + + Profile Completed: {statusData?.profileStatus ? "Yes ✅" : "No ❌"} + + + + {/* Custom Modal */} + {showModal && ( + setShowModal(false)} + > + + + Complete Your Profile + + Your profile is incomplete. Please update your details to continue. + + + + Go to Profile + + + setShowModal(false)} + > + Close + + + + + )} + + ); +}; + + +export default UserProfileStatus; + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 16, + backgroundColor: "#f5f7fb", + }, + centered: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + title: { + fontSize: 22, + fontWeight: "700", + marginBottom: 20, + }, + card: { + backgroundColor: "#fff", + padding: 16, + borderRadius: 12, + elevation: 3, + shadowOpacity: 0.1, + shadowRadius: 6, + marginBottom: 20, + }, + cardTitle: { + fontSize: 18, + fontWeight: "600", + marginBottom: 10, + }, + button: { + backgroundColor: "#007AFF", + paddingVertical: 10, + paddingHorizontal: 20, + borderRadius: 8, + alignItems: "center", + }, + buttonText: { + color: "#fff", + fontWeight: "600", + }, + errorText: { + color: "red", + fontSize: 16, + marginBottom: 12, + }, + modalOverlay: { + flex: 1, + backgroundColor: "rgba(0,0,0,0.4)", + justifyContent: "center", + alignItems: "center", + }, + modalBox: { + width: "85%", + backgroundColor: "#fff", + borderRadius: 16, + padding: 20, + alignItems: "center", + elevation: 5, + }, + modalTitle: { + fontSize: 20, + fontWeight: "700", + marginBottom: 10, + color: "#FF9500", + }, + modalText: { + fontSize: 14, + color: "#444", + textAlign: "center", + marginBottom: 20, + }, +}); diff --git a/src/screens/profile/BecomeOrg.tsx b/src/screens/profile/BecomeOrg.tsx new file mode 100644 index 0000000..cbe6225 --- /dev/null +++ b/src/screens/profile/BecomeOrg.tsx @@ -0,0 +1,143 @@ +import React, { useState } from "react"; +import { + View, + Text, + StyleSheet, + Alert, + ActivityIndicator, + ScrollView, + KeyboardAvoidingView, + Platform, + SafeAreaView, +} from "react-native"; +import * as Keychain from "react-native-keychain"; +import { CustomInput, CustomButton } from "../../components/common/Custom"; +import colors from "../../components/colors"; + +const BecomeOrg = () => { + const [bankAccountNumber, setBankAccountNumber] = useState(""); + const [bankName, setBankName] = useState(""); + const [branchCode, setBranchCode] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSubmit = async () => { + if (!bankAccountNumber || !bankName || !branchCode) { + Alert.alert("Error", "Please fill all fields"); + return; + } + + try { + setLoading(true); + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + const response = await fetch( + "https://api.godemo.in/FundCollect/api/organizer/registration/apply", + { + method: "POST", + headers: { + Accept: "*/*", + "Content-Type": "application/json", + Authorization: `Bearer ${token}`, + }, + body: JSON.stringify({ + bankAccountNumber, + bankName, + branchCode, + }), + } + ); + + const result = await response.json(); + if (result.status === "success") { + Alert.alert("Success", "Organizer registration applied successfully"); + setBankAccountNumber(""); + setBankName(""); + setBranchCode(""); + } else { + Alert.alert("Error", result.message || "Failed to apply"); + } + } catch (error) { + console.error("❌ Registration error:", error); + Alert.alert("Error", "Something went wrong"); + } finally { + setLoading(false); + } + }; + + return ( + + + + Organizer Registration + + + + + + + + + + {loading && ( + + )} + + + + ); +}; + +export default BecomeOrg; + +const styles = StyleSheet.create({ + container: { + flexGrow: 1, + padding: 20, + justifyContent: "center", + }, + title: { + fontSize: 22, + fontWeight: "700", + marginBottom: 20, + textAlign: "center", + color: colors.textDark, + }, +}); diff --git a/src/screens/profile/Profile.tsx b/src/screens/profile/Profile.tsx index 1a30dec..5805f9e 100644 --- a/src/screens/profile/Profile.tsx +++ b/src/screens/profile/Profile.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useCallback } from "react"; import { StyleSheet, View, @@ -18,6 +18,10 @@ import { Ionicons } from "@react-native-vector-icons/ionicons"; import { launchImageLibrary } from "react-native-image-picker"; import { base_URL } from "../../utils/ApiUrls"; import { CustomButton, CustomInput } from "../../components/common/Custom"; +import colors from "../../components/colors"; +import SwitchNavigator from "./SwitchNavigator"; +import { useFocusEffect } from "@react-navigation/native"; +import { ApiResponse } from "../home/UserProfileStatus"; @@ -45,6 +49,9 @@ const Profile = () => { const [fetching, setFetching] = useState(true); const [editing, setEditing] = useState(false); const [imageUpdating, setImageUpdating] = useState(false); + const [loading, setLoading] = useState(false); + const [statusData, setStatusData] = useState(null); + const [error, setError] = useState(null); const fetchUserProfile = async () => { try { @@ -198,6 +205,48 @@ const Profile = () => { } }; + const userStatus = async () => { + try { + const credentials = await Keychain.getGenericPassword(); + if (!credentials) { + Alert.alert("Error", "You are not logged in"); + return; + } + const token = credentials.password; + + setLoading(true); + setError(null); + const response = await fetch(`${base_URL}user/profile/status`, { + method: "GET", + headers: { + Accept: "*/*", + Authorization: `Bearer ${token}`, + }, + }); + + const json: ApiResponse = await response.json(); + + console.log("👤 Profile Status Response:", json); + if (json.status === "success") { + setStatusData(json.data); + + } else { + setError(json.message || "Failed to fetch status"); + } + } catch (err) { + setError("Something went wrong. Please try again."); + } finally { + setLoading(false); + } + }; + + useFocusEffect( + useCallback(() => { + userStatus(); + }, []) + ); + + console.log("login user data :", user); useEffect(() => { fetchUserProfile(); @@ -243,9 +292,15 @@ const Profile = () => { contentContainerStyle={{ flexGrow: 1 }} keyboardShouldPersistTaps="handled" > + {/* Profile Card */} {/* Profile Image Section */} + { + statusData?.role && statusData?.organizerStatus ? + Your Role: {statusData.role} | Organizer Status: {statusData.organizerStatus} + : + } { onPress={handleUpdateImage} icon={imageUpdating ? undefined : "cloud-upload"} style={styles.updateImageButton} - // disabled={imageUpdating} + buttonBackground="#6366f1" /> @@ -340,6 +395,7 @@ const Profile = () => { onPress={() => setEditing(true)} icon="create" style={styles.editButton} + buttonBackground="#6366f1" /> ) : ( @@ -402,13 +458,14 @@ const Profile = () => { onPress={updateProfile} icon="save" style={styles.saveButton} + buttonBackground="#10b981" /> setEditing(false)} icon="close" style={styles.cancelButton} + buttonBackground="#f38080ff" /> @@ -423,7 +480,7 @@ const Profile = () => { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: "#f8fafc", + backgroundColor: colors.pastelBlue, marginBottom: '30%', }, loadingContainer: { @@ -506,6 +563,8 @@ const styles = StyleSheet.create({ }, updateImageButton: { width: "80%", + paddingHorizontal: 10, + borderRadius: 25, }, detailsSection: { marginBottom: 24, diff --git a/src/screens/profile/SwitchNavigator.tsx b/src/screens/profile/SwitchNavigator.tsx new file mode 100644 index 0000000..5a26a3a --- /dev/null +++ b/src/screens/profile/SwitchNavigator.tsx @@ -0,0 +1,67 @@ +import React, { useState } from "react"; +import { View, Text, StyleSheet, Switch } from "react-native"; +import { useNavigation } from "@react-navigation/native"; +import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; + +type RootStackParamList = { + BecomeOrg: undefined; + Profile: undefined; +}; + +type SwitchNavigatorProps = { + role: string; + status: string; +}; + +const SwitchNavigator: React.FC = ({ role, status }) => { + const navigation = + useNavigation>(); + const [isEnabled, setIsEnabled] = useState(false); + + const toggleSwitch = () => { + const newValue = !isEnabled; + setIsEnabled(newValue); + + if (newValue) { + navigation.navigate("BecomeOrg"); // Navigate when ON + } else { + navigation.navigate("Profile"); // Navigate when OFF + } + }; + + console.log("status :", status); + console.log("role :", role); + + return ( + + + {!isEnabled && role !== "USER" + ? "Become an Organizer" + : `Request Organizer Role ${status}`} + + + + ); +}; + +export default SwitchNavigator; + +const styles = StyleSheet.create({ + container: { + flexDirection: "row", + justifyContent: 'flex-end', + paddingHorizontal: 16, + paddingVertical: 10, + gap: 10, + }, + text: { + fontSize: 18, + flexShrink: 1, // Wrap text if too long + }, +});