Add Edit Name
parent
ad7d59b7a5
commit
b7ce82b86e
|
|
@ -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();
|
||||
}, []);
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
<KeyboardAvoidingView
|
||||
style={styles.container}
|
||||
behavior={Platform.OS === "ios" ? "padding" : "height"}
|
||||
>
|
||||
<TouchableWithoutFeedback onPress={Keyboard.dismiss}>
|
||||
<View style={styles.inner}>
|
||||
<Formik
|
||||
initialValues={{ name: originalName }}
|
||||
validationSchema={nameSchema}
|
||||
onSubmit={handleSave}
|
||||
enableReinitialize
|
||||
>
|
||||
{({
|
||||
handleChange,
|
||||
handleBlur,
|
||||
handleSubmit,
|
||||
values,
|
||||
touched,
|
||||
errors,
|
||||
}) => {
|
||||
const hasChanged = values.name !== originalName;
|
||||
const hasError = !!errors.name;
|
||||
|
||||
return (
|
||||
<View style={styles.formContainer}>
|
||||
<View style={styles.inputContainer}>
|
||||
<Text style={styles.label}>Enter Name</Text>
|
||||
<TextInput
|
||||
style={[
|
||||
styles.input,
|
||||
{
|
||||
borderColor:
|
||||
touched.name && errors.name ? "#D51D10" : "#D8DDE7",
|
||||
},
|
||||
]}
|
||||
value={values.name}
|
||||
onChangeText={handleChange("name")}
|
||||
onBlur={handleBlur("name")}
|
||||
placeholder="Enter your name"
|
||||
placeholderTextColor="#949CAC"
|
||||
/>
|
||||
{touched.name && errors.name && (
|
||||
<Text style={styles.error}>{errors.name}</Text>
|
||||
)}
|
||||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
onPress={handleSubmit as unknown as () => void}
|
||||
style={[
|
||||
styles.button,
|
||||
hasChanged && !hasError
|
||||
? styles.buttonEnabled
|
||||
: styles.buttonDisabled,
|
||||
{ marginBottom: insets.bottom },
|
||||
]}
|
||||
disabled={!hasChanged || hasError}
|
||||
>
|
||||
<Text style={styles.buttonText}>Save</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}}
|
||||
</Formik>
|
||||
</View>
|
||||
</TouchableWithoutFeedback>
|
||||
</KeyboardAvoidingView>
|
||||
);
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
|
@ -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 (
|
||||
<>
|
||||
<ScrollView contentContainerStyle={styles.scrollContent}>
|
||||
|
|
@ -26,16 +36,20 @@ export default function ProfileScreen() {
|
|||
style={styles.avatar}
|
||||
/>
|
||||
<TouchableOpacity style={styles.editAvatar}>
|
||||
<MaterialIcons name="edit" size={20} color="#FDFDFD" />
|
||||
<EditIcon />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={styles.card}>
|
||||
<View style={styles.row}>
|
||||
<View style={styles.textGroup}>
|
||||
<Text style={styles.label}>Name</Text>
|
||||
<Text style={styles.value}>Amar Kesari</Text>
|
||||
<Text style={styles.value}>{userName}</Text>
|
||||
</View>
|
||||
<TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
onPress={() => {
|
||||
router.push("/user/edit_name");
|
||||
}}
|
||||
>
|
||||
<MaterialIcons name="edit" size={20} color="#555C70" />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
|
|
@ -43,7 +57,7 @@ export default function ProfileScreen() {
|
|||
<View style={styles.row}>
|
||||
<View style={styles.textGroup}>
|
||||
<Text style={styles.label}>Mobile Number</Text>
|
||||
<Text style={styles.value}>9876543210</Text>
|
||||
<Text style={styles.value}>{mobileNumber}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -113,7 +127,7 @@ const styles = StyleSheet.create({
|
|||
editAvatar: {
|
||||
position: "absolute",
|
||||
bottom: 0,
|
||||
right: 105,
|
||||
right: "35%",
|
||||
width: 40,
|
||||
height: 40,
|
||||
backgroundColor: "#008866",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<mask id="mask0_14_4327" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="20" height="20">
|
||||
<rect width="20" height="20" fill="#D9D9D9"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_14_4327)">
|
||||
<path d="M4.16667 15.8333H5.35417L13.5 7.6875L12.3125 6.5L4.16667 14.6458V15.8333ZM3.33333 17.5C3.09722 17.5 2.89931 17.4201 2.73958 17.2604C2.57986 17.1007 2.5 16.9028 2.5 16.6667V14.6458C2.5 14.4236 2.54167 14.2118 2.625 14.0104C2.70833 13.809 2.82639 13.6319 2.97917 13.4792L13.5 2.97917C13.6667 2.82639 13.8507 2.70833 14.0521 2.625C14.2535 2.54167 14.4653 2.5 14.6875 2.5C14.9097 2.5 15.125 2.54167 15.3333 2.625C15.5417 2.70833 15.7222 2.83333 15.875 3L17.0208 4.16667C17.1875 4.31944 17.309 4.5 17.3854 4.70833C17.4618 4.91667 17.5 5.125 17.5 5.33333C17.5 5.55556 17.4618 5.76736 17.3854 5.96875C17.309 6.17014 17.1875 6.35417 17.0208 6.52083L6.52083 17.0208C6.36806 17.1736 6.19097 17.2917 5.98958 17.375C5.78819 17.4583 5.57639 17.5 5.35417 17.5H3.33333ZM12.8958 7.10417L12.3125 6.5L13.5 7.6875L12.8958 7.10417Z" fill="#FCFCFC"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -199,6 +199,7 @@ export const {
|
|||
clearSendOTPError,
|
||||
clearVerifyOTPError,
|
||||
clearAllErrors,
|
||||
setIsLoggedIn,
|
||||
} = authSlice.actions;
|
||||
|
||||
export default authSlice.reducer;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -36,8 +36,10 @@ export const getUserDetails = createAsyncThunk<UserData>(
|
|||
"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<string>) => {
|
||||
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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue