From b7ce82b86ef0b21ef04779a99881606c9744acc6 Mon Sep 17 00:00:00 2001 From: vinay kumar Date: Mon, 7 Jul 2025 12:16:01 +0530 Subject: [PATCH] Add Edit Name --- app/_layout.tsx | 27 +++++- app/user/edit_name.tsx | 183 +++++++++++++++++++++++++++++++++++++++++ app/user/profile.tsx | 24 ++++-- assets/icons/edit.svg | 8 ++ store/authSlice.ts | 1 + store/rootReducer.ts | 2 + store/userSlice.ts | 11 ++- 7 files changed, 246 insertions(+), 10 deletions(-) create mode 100644 app/user/edit_name.tsx create mode 100644 assets/icons/edit.svg diff --git a/app/_layout.tsx b/app/_layout.tsx index f96ea3c..1dfe892 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -11,6 +11,7 @@ import * as SplashScreen from "expo-splash-screen"; import AsyncStorage from "@react-native-async-storage/async-storage"; import { STORAGE_KEYS } from "@/constants/config"; import { setIsLoggedIn } from "@/store/authSlice"; +import { getUserDetails } from "@/store/userSlice"; SplashScreen.preventAutoHideAsync(); @@ -35,11 +36,29 @@ function SplashAndAuthRouter() { useEffect(() => { const loadAuth = async () => { - const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); - dispatch(setIsLoggedIn(!!token)); - setLoading(false); - SplashScreen.hideAsync(); + try { + const token = await AsyncStorage.getItem(STORAGE_KEYS.AUTH_TOKEN); + + if (!token) { + dispatch(setIsLoggedIn(false)); + return; + } + + // Token exists, now verify it by calling getUserDetails + // await dispatch(getUserDetails()).unwrap(); + + // If it reaches here, the token is valid + dispatch(setIsLoggedIn(true)); + } catch (error) { + // getUserDetails failed => token is invalid + dispatch(setIsLoggedIn(false)); + await AsyncStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN); // optionally clean up + } finally { + setLoading(false); + SplashScreen.hideAsync(); + } }; + loadAuth(); }, []); diff --git a/app/user/edit_name.tsx b/app/user/edit_name.tsx new file mode 100644 index 0000000..0944b1e --- /dev/null +++ b/app/user/edit_name.tsx @@ -0,0 +1,183 @@ +import React from "react"; +import { + View, + Text, + TextInput, + StyleSheet, + TouchableOpacity, + KeyboardAvoidingView, + Platform, + Keyboard, + TouchableWithoutFeedback, +} from "react-native"; +import { useDispatch, useSelector } from "react-redux"; +import { RootState } from "@/store"; +import { Formik } from "formik"; +import * as Yup from "yup"; +import api from "@/services/axiosClient"; +import { BASE_URL } from "@/constants/config"; +import { setUsername } from "@/store/userSlice"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; +import { router } from "expo-router"; +import { useSnackbar } from "@/contexts/Snackbar"; + +export default function EditName() { + const { data } = useSelector((state: RootState) => state.user); + const originalName = data?.name || ""; + const dispatch = useDispatch(); + const insets = useSafeAreaInsets(); + const nameSchema = Yup.object().shape({ + name: Yup.string().required("Name is required"), + }); + const { showSnackbar } = useSnackbar(); + const handleSave = async (values: { name: string }) => { + try { + await api.put(`${BASE_URL}/api/v1/update-user-information`, { + name: values.name, + mobile: data?.mobile, + }); + + dispatch(setUsername(values.name)); + showSnackbar("Name updated successfully", "success"); + router.back(); + } catch (error) { + console.error("Error updating name:", error); + } + }; + + return ( + + + + + {({ + handleChange, + handleBlur, + handleSubmit, + values, + touched, + errors, + }) => { + const hasChanged = values.name !== originalName; + const hasError = !!errors.name; + + return ( + + + Enter Name + + {touched.name && errors.name && ( + {errors.name} + )} + + + void} + style={[ + styles.button, + hasChanged && !hasError + ? styles.buttonEnabled + : styles.buttonDisabled, + { marginBottom: insets.bottom }, + ]} + disabled={!hasChanged || hasError} + > + Save + + + ); + }} + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#F3F5F8", + paddingHorizontal: 16, + }, + inner: { + flex: 1, + justifyContent: "space-between", + paddingVertical: 24, + }, + formContainer: { + flex: 1, + justifyContent: "space-between", + }, + inputContainer: { + marginBottom: 24, + }, + label: { + fontSize: 14, + marginBottom: 8, + color: "#252A34", + fontFamily: "Inter", + lineHeight: 20, + }, + input: { + backgroundColor: "#ffffff", + borderRadius: 4, + borderWidth: 1, + height: 40, + paddingHorizontal: 8, + fontSize: 14, + fontFamily: "Inter", + fontWeight: "500", + lineHeight: 20, + }, + error: { + marginTop: 8, + color: "#D51D10", + fontSize: 12, + fontFamily: "Inter", + fontWeight: "bold", + lineHeight: 20, + }, + button: { + height: 48, + borderRadius: 4, + alignItems: "center", + justifyContent: "center", + width: "100%", + paddingHorizontal: 16, + }, + buttonEnabled: { + backgroundColor: "#008761", + }, + buttonDisabled: { + backgroundColor: "#B0B7C5", // from Figma: rgba(176, 183, 197) + }, + buttonText: { + color: "#FCFCFC", + fontSize: 14, + fontFamily: "Inter", + fontWeight: "bold", + lineHeight: 20, + }, +}); diff --git a/app/user/profile.tsx b/app/user/profile.tsx index 3d0cc37..f35d52d 100644 --- a/app/user/profile.tsx +++ b/app/user/profile.tsx @@ -9,6 +9,10 @@ import { } from "react-native"; import { MaterialIcons } from "@expo/vector-icons"; import LanguageModal from "@/components/Profile/LangaugeModal"; +import { useSelector } from "react-redux"; +import { RootState } from "@/store"; +import EditIcon from "../../assets/icons/edit.svg"; +import { router } from "expo-router"; export default function ProfileScreen() { const [isLangaugeModalVisible, setLanguageModalVisible] = @@ -17,6 +21,12 @@ export default function ProfileScreen() { const toggleLanguageModal = () => { setLanguageModalVisible(!isLangaugeModalVisible); }; + + const { data } = useSelector((state: RootState) => state.user); + + const userName = data?.name || "User"; + const mobileNumber = data?.mobile || "Not provided"; + return ( <> @@ -26,16 +36,20 @@ export default function ProfileScreen() { style={styles.avatar} /> - + Name - Amar Kesari + {userName} - + { + router.push("/user/edit_name"); + }} + > @@ -43,7 +57,7 @@ export default function ProfileScreen() { Mobile Number - 9876543210 + {mobileNumber} @@ -113,7 +127,7 @@ const styles = StyleSheet.create({ editAvatar: { position: "absolute", bottom: 0, - right: 105, + right: "35%", width: 40, height: 40, backgroundColor: "#008866", diff --git a/assets/icons/edit.svg b/assets/icons/edit.svg new file mode 100644 index 0000000..aa9db27 --- /dev/null +++ b/assets/icons/edit.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/store/authSlice.ts b/store/authSlice.ts index 9fa31fa..df875e4 100644 --- a/store/authSlice.ts +++ b/store/authSlice.ts @@ -199,6 +199,7 @@ export const { clearSendOTPError, clearVerifyOTPError, clearAllErrors, + setIsLoggedIn, } = authSlice.actions; export default authSlice.reducer; diff --git a/store/rootReducer.ts b/store/rootReducer.ts index 3eb108c..e30f68a 100644 --- a/store/rootReducer.ts +++ b/store/rootReducer.ts @@ -1,10 +1,12 @@ import { combineReducers } from "@reduxjs/toolkit"; import authreducer from "./authSlice"; import telemetryReducer from "./telemetrySlice"; +import userReducer from "./userSlice"; const rootReducer = combineReducers({ auth: authreducer, telemetry: telemetryReducer, + user: userReducer, }); export default rootReducer; diff --git a/store/userSlice.ts b/store/userSlice.ts index da7c1bb..49dae8a 100644 --- a/store/userSlice.ts +++ b/store/userSlice.ts @@ -36,8 +36,10 @@ export const getUserDetails = createAsyncThunk( "user/getUserDetails", async (_, { rejectWithValue }) => { try { + console.log("Fetching user details from API..."); const response = await api.get(`${BASE_URL}/api/v1/get-user-details`); if (response.data.success) { + console.log("User details fetched successfully:", response.data.data); return response.data.data; } else { return rejectWithValue("Failed to fetch user data"); @@ -61,6 +63,13 @@ const userSlice = createSlice({ state.error = null; state.loading = false; }, + setUsername: (state, action: PayloadAction) => { + if (state.data) { + state.data.name = action.payload; + } else { + console.warn("Cannot set username, user data is not loaded"); + } + }, }, extraReducers: (builder) => { builder @@ -82,5 +91,5 @@ const userSlice = createSlice({ }, }); -export const { clearUser } = userSlice.actions; +export const { clearUser, setUsername } = userSlice.actions; export default userSlice.reducer;