diff --git a/app/api/profileApi.ts b/app/api/profileApi.ts
index 3e3f113..ff50581 100644
--- a/app/api/profileApi.ts
+++ b/app/api/profileApi.ts
@@ -11,7 +11,7 @@ export const updateProfileApi = async (
if (payload.firstname !== undefined) formData.append('firstname', payload.firstname);
if (payload.lastname !== undefined) formData.append('lastname', payload.lastname);
- if (payload.phone !== undefined) formData.append('phone', payload.phone);
+ if (payload.phonenumber !== undefined) formData.append('phonenumber', payload.phonenumber);
if (payload.email_signature !== undefined) formData.append('email_signature', payload.email_signature);
if (payload.password) formData.append('password', payload.password);
if (payload.passwordr) formData.append('passwordr', payload.passwordr);
diff --git a/app/components/projectActivityFeed/projectActivityFeed.tsx b/app/components/projectActivityFeed/projectActivityFeed.tsx
index 69b21d7..621d201 100644
--- a/app/components/projectActivityFeed/projectActivityFeed.tsx
+++ b/app/components/projectActivityFeed/projectActivityFeed.tsx
@@ -3,7 +3,6 @@ import {
View,
Text,
ActivityIndicator,
- ScrollView,
TouchableOpacity,
} from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
@@ -143,11 +142,7 @@ export const ProjectActivityFeed = ({
) : (
<>
-
+
{displayData.map((item, index) => (
))}
-
+
{hasMore && (
diff --git a/app/features/editProfile/editProfile.screen.tsx b/app/features/editProfile/editProfile.screen.tsx
index 329dd2c..e95cc84 100644
--- a/app/features/editProfile/editProfile.screen.tsx
+++ b/app/features/editProfile/editProfile.screen.tsx
@@ -15,7 +15,7 @@ import Icon from 'react-native-vector-icons/Ionicons';
import { useNavigation } from '@react-navigation/native';
import { useTheme } from '@theme';
import { FormInput, Loader } from '@components';
-import { useAppDispatch, useAppSelector, RootState, updateUserData } from '@store';
+import { useAppDispatch, useAppSelector, RootState } from '@store';
import { getStyles } from './editProfile.styles';
import { updateProfile, getStaffDetails, resetEditProfileState } from './thunk';
import ImagePicker from 'react-native-image-crop-picker';
@@ -43,6 +43,9 @@ export const EditProfileScreen = () => {
const [profileImageMime, setProfileImageMime] = useState('image/jpeg');
const [profileImageName, setProfileImageName] = useState('profile_image.jpg');
const [isImageChanged, setIsImageChanged] = useState(false);
+ // UI toggle state
+ const [showPassword, setShowPassword] = useState(false);
+ const [showConfirmPassword, setShowConfirmPassword] = useState(false);
const resolveServerUrl = async (path?: string | null) => {
if (!path) return '';
@@ -62,36 +65,26 @@ export const EditProfileScreen = () => {
return `${cleanBase}/${cleanRelPath}`;
};
- useEffect(() => {
- resolveServerUrl(userData?.profile_image).then((url) => {
- setProfileImageUri(url);
- });
- }, [userData?.profile_image]);
-
- // UI toggle state
- const [showPassword, setShowPassword] = useState(false);
- const [showConfirmPassword, setShowConfirmPassword] = useState(false);
-
// Fetch staff details on mount
- // useEffect(() => {
- // if (userData?.staffid) {
- // dispatch(getStaffDetails(userData.staffid));
- // }
- // }, [dispatch, userData?.staffid]);
+ useEffect(() => {
+ if (userData?.staffid) {
+ dispatch(getStaffDetails(userData.staffid));
+ }
+ }, [dispatch, userData?.staffid]);
// Update local form state when details are loaded from the server
- // useEffect(() => {
- // if (staffDetails) {
- // setFirstname(staffDetails.firstname || '');
- // setLastname(staffDetails.lastname || '');
- // setEmail(staffDetails.email || '');
- // setPhone(staffDetails.phonenumber || '');
- // setEmailSignature(staffDetails.email_signature || '');
- // resolveServerUrl(userData?.profile_image).then((url) => {
- // setProfileImageUri(url);
- // });
- // }
- // }, [staffDetails, userData?.profile_image]);
+ useEffect(() => {
+ if (staffDetails) {
+ setFirstname(staffDetails.firstname || '');
+ setLastname(staffDetails.lastname || '');
+ setEmail(staffDetails.email || '');
+ setPhone(staffDetails.phonenumber || '');
+ setEmailSignature(staffDetails.email_signature || '');
+ resolveServerUrl(userData?.profile_image).then((url) => {
+ setProfileImageUri(url);
+ });
+ }
+ }, [staffDetails, userData?.profile_image]);
// Reset edit profile state on unmount
useEffect(() => {
@@ -100,6 +93,34 @@ export const EditProfileScreen = () => {
};
}, [dispatch]);
+ // Navigate back on success
+ useEffect(() => {
+ if (successMessage) {
+ Alert.alert('Success', successMessage, [
+ {
+ text: 'OK',
+ onPress: () => {
+ dispatch(resetEditProfileState());
+ navigation.goBack();
+ },
+ },
+ ]);
+ }
+ }, [successMessage]);
+
+ // Show error message from state
+ useEffect(() => {
+ if (error) {
+ Alert.alert('Error', error, [
+ {
+ text: 'OK',
+ onPress: () => dispatch(resetEditProfileState()),
+ },
+ ]);
+ }
+ }, [error]);
+
+
const handleCameraLaunch = () => {
ImagePicker.openCamera({
width: 400,
@@ -203,40 +224,13 @@ export const EditProfileScreen = () => {
email,
firstname: firstname || undefined,
lastname: lastname || undefined,
- phone: phone || undefined,
+ phonenumber: phone || undefined,
email_signature: emailSignature || undefined,
password: password || undefined,
passwordr: passwordr || undefined,
profile_image: profileImagePayload,
}),
- )
- .unwrap()
- .then((response) => {
- // Success
- const updatedImage = response.user_data?.profile_image || userData?.profile_image;
- dispatch(
- updateUserData({
- firstname,
- lastname,
- email,
- phonenumber: phone,
- email_signature: emailSignature,
- profile_image: updatedImage,
- }),
- );
- Alert.alert('Success', 'Profile updated successfully.', [
- {
- text: 'OK',
- onPress: () => {
- dispatch(resetEditProfileState());
- navigation.goBack();
- },
- },
- ]);
- })
- .catch((err) => {
- Alert.alert('Error', err || 'Failed to update profile');
- });
+ );
};
const staffId = userData?.staffid || '';
diff --git a/app/features/leads/leads.screen.tsx b/app/features/leads/leads.screen.tsx
index 9a8045b..a97ffb3 100644
--- a/app/features/leads/leads.screen.tsx
+++ b/app/features/leads/leads.screen.tsx
@@ -114,9 +114,9 @@ export const LeadsScreen = () => {
placeholder="Search leads, companies, emails..."
style={styles.searchInput}
/>
-
+ {/*
-
+ */}
{/* Status count cards */}
diff --git a/app/features/login/login.screen.tsx b/app/features/login/login.screen.tsx
index f96e0cf..51b423d 100644
--- a/app/features/login/login.screen.tsx
+++ b/app/features/login/login.screen.tsx
@@ -17,8 +17,8 @@ import { useTheme } from '@theme';
import { useAppDispatch, useAppSelector, RootState, login } from '@store';
import { LoginRequest } from '@interfaces';
import { NotificationService } from '@services';
-import { backGroundImage } from '@utils';
-import { FormInput } from '@components';
+import { backGroundImage, TENANCY_OPTIONS } from '@utils';
+import { FormInput, FormPicker } from '@components';
import AsyncStorage from '@react-native-async-storage/async-storage';
import DeviceInfo from 'react-native-device-info';
@@ -97,8 +97,14 @@ export const LoginScreen = () => {
const savedPassword = await AsyncStorage.getItem('password');
if (savedBaseUrl) {
- const cleanBaseUrl = savedBaseUrl.replace(/^(https?:\/\/)?/, '');
- setTenancy(cleanBaseUrl);
+ const matched = TENANCY_OPTIONS.find(
+ opt =>
+ opt.value.trim().replace(/\/+$/, '').toLowerCase() ===
+ savedBaseUrl.trim().replace(/\/+$/, '').toLowerCase(),
+ );
+ setTenancy(matched ? matched.value : savedBaseUrl);
+ } else if (TENANCY_OPTIONS.length > 0) {
+ setTenancy(TENANCY_OPTIONS[0].value);
}
if (savedEmail) setEmail(savedEmail);
if (savedPassword) setPassword(savedPassword);
@@ -180,39 +186,13 @@ export const LoginScreen = () => {
) : null}
- {/* Tenancy Field */}
- {
- const cleaned = text.replace(/^(https?:\/\/)?/, '');
- setTenancy(cleaned);
- }}
- prefix="https://"
- placeholder="company-name"
- autoCapitalize="none"
- autoCorrect={false}
- containerStyle={styles.fieldContainer}
- inputContainerStyle={[
- styles.inputWrapper,
- focusedField === 'tenancy' && styles.inputWrapperFocused,
- ]}
- onFocus={() => {
- setFocusedField('tenancy');
- setTimeout(() => scrollViewRef.current?.scrollToEnd({ animated: true }), 100);
- }}
- onBlur={() => setFocusedField(null)}
- leftIcon={
-
- }
+ onValueChange={(val) => setTenancy(val)}
+ options={TENANCY_OPTIONS}
+ placeholder="Select Tenancy Environment"
/>
{/* Email Field */}
diff --git a/app/interfaces/profile.ts b/app/interfaces/profile.ts
index 0c65ab0..9f4a864 100644
--- a/app/interfaces/profile.ts
+++ b/app/interfaces/profile.ts
@@ -3,7 +3,7 @@ export interface UpdateProfilePayload {
email: string;
firstname?: string;
lastname?: string;
- phone?: string;
+ phonenumber?: string;
email_signature?: string;
password?: string;
passwordr?: string;
diff --git a/app/navigation/customDrawerContent.style.ts b/app/navigation/customDrawerContent.style.ts
index 0440bff..de761eb 100644
--- a/app/navigation/customDrawerContent.style.ts
+++ b/app/navigation/customDrawerContent.style.ts
@@ -156,4 +156,10 @@ export const getStyles = (colors: ThemeColors) =>
fontWeight: '700',
color: '#EF4444',
},
+ versionText: {
+ fontSize: 11,
+ color: colors.textMuted,
+ textAlign: 'center',
+ marginTop: 10,
+ },
});
diff --git a/app/navigation/customDrawerContent.tsx b/app/navigation/customDrawerContent.tsx
index b82988d..f4bfc3f 100644
--- a/app/navigation/customDrawerContent.tsx
+++ b/app/navigation/customDrawerContent.tsx
@@ -222,6 +222,7 @@ export const CustomDrawerContent = (props: DrawerContentComponentProps) => {
/>
Sign Out
+ App Version: 1.0.0
);
diff --git a/app/store/commonReducers/auth/reducers.ts b/app/store/commonReducers/auth/reducers.ts
index fe4f1cf..df47d54 100644
--- a/app/store/commonReducers/auth/reducers.ts
+++ b/app/store/commonReducers/auth/reducers.ts
@@ -1,9 +1,8 @@
-import { createAction, createReducer } from '@reduxjs/toolkit';
+import { createReducer } from '@reduxjs/toolkit';
import { login } from './thunk';
import { UserData } from '@interfaces';
+import { updateProfile } from '../../../features/editProfile/thunk';
-// Patch user_data in auth state after a successful profile update
-export const updateUserData = createAction>('auth/updateUserData');
export interface LoginState {
loginSuccess: boolean;
@@ -50,9 +49,10 @@ export const authReducer = createReducer(initialState, builder => {
acc.loginMessage = (payload as string) ?? error.message ?? 'Login failed';
acc.loginSuccess = false;
})
- .addCase(updateUserData, (acc, action) => {
- if (acc.user_data) {
- acc.user_data = { ...acc.user_data, ...action.payload };
+ // Sync auth user_data after a successful profile update
+ .addCase(updateProfile.fulfilled, (acc, action) => {
+ if (acc.user_data && action.payload.user_data) {
+ acc.user_data = { ...acc.user_data, ...action.payload.user_data };
}
});
});
diff --git a/app/utils/api.ts b/app/utils/api.ts
index 7dde230..2b8f3a4 100644
--- a/app/utils/api.ts
+++ b/app/utils/api.ts
@@ -1,22 +1,26 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
-import config from 'react-native-config';
import AsyncStorage from '@react-native-async-storage/async-storage';
+import { getAuthTokenForBaseUrl } from '@utils';
import {Store} from '@reduxjs/toolkit';
const axiosInstance: AxiosInstance = axios.create({
timeout: 15000,
headers: {
'Content-Type': 'application/json',
- authtoken: config.AUTH_TOKEN,
},
});
axiosInstance.interceptors.request.use(
async request => {
- // Dynamically set baseURL from AsyncStorage on every request
+ // Dynamically set baseURL and authtoken from AsyncStorage on every request
const storedBaseUrl = await AsyncStorage.getItem('base_url');
if (storedBaseUrl) {
request.baseURL = storedBaseUrl;
+
+ const authToken = getAuthTokenForBaseUrl(storedBaseUrl);
+ if (authToken) {
+ request.headers.authtoken = authToken;
+ }
}
if (request.data instanceof FormData) {
diff --git a/app/utils/enviroment.ts b/app/utils/enviroment.ts
new file mode 100644
index 0000000..f853a70
--- /dev/null
+++ b/app/utils/enviroment.ts
@@ -0,0 +1,33 @@
+import config from 'react-native-config';
+
+export const normalizeUrl = (url?: string): string => {
+ if (!url) return '';
+ return url.trim().replace(/\/+$/, '').toLowerCase();
+};
+
+export const getAuthTokenForBaseUrl = (baseUrl: string | null): string => {
+ if (!baseUrl) return '';
+
+ const currentUrl = normalizeUrl(baseUrl);
+
+ const envConfigs = [
+ {
+ baseUrl: config.DEMO_CONVEXCRM_BASE_URL,
+ authToken: config.DEMO_CONVEXCRM_AUTH_TOKEN,
+ },
+ {
+ baseUrl: config.CONVEXSOL_CONVEXCRM_BASE_URL,
+ authToken: config.CONVEXSOL_CONVEXCRM_AUTH_TOKEN,
+ },
+ {
+ baseUrl: config.INTERNAL_CONVEXCRM_BASE_URL,
+ authToken: config.INTERNAL_CONVEXCRM_AUTH_TOKEN,
+ },
+ ];
+
+ const matched = envConfigs.find(
+ item => item.baseUrl && normalizeUrl(item.baseUrl) === currentUrl,
+ );
+
+ return matched?.authToken || '';
+};
diff --git a/app/utils/index.ts b/app/utils/index.ts
index 077b00a..1ae357a 100644
--- a/app/utils/index.ts
+++ b/app/utils/index.ts
@@ -3,3 +3,5 @@ export * from './assets';
export * from './api';
export * from './helper';
export * from './navigationRef';
+export * from './tenencyOptions';
+export * from './enviroment';
diff --git a/app/utils/tenencyOptions.ts b/app/utils/tenencyOptions.ts
new file mode 100644
index 0000000..47434ba
--- /dev/null
+++ b/app/utils/tenencyOptions.ts
@@ -0,0 +1,17 @@
+import config from 'react-native-config';
+import { FormPickerOption } from '@components';
+
+export const TENANCY_OPTIONS: FormPickerOption[] = [
+ {
+ label: 'Demo Convex CRM (demo-convexcrm.convexsol.co)',
+ value: config.DEMO_CONVEXCRM_BASE_URL || '',
+ },
+ {
+ label: 'ConvexSol CRM (convexsol.convexcrm.com)',
+ value: config.CONVEXSOL_CONVEXCRM_BASE_URL || '',
+ },
+ {
+ label: 'Internal CRM (internal.convexcrm.com)',
+ value: config.INTERNAL_CONVEXCRM_BASE_URL || '',
+ },
+];