Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"@react-native-async-storage/async-storage": "^1.24.0",
"@react-native-clipboard/clipboard": "^1.14.2",
"@react-native-community/cli": "^13.6.9",
"@react-native-community/datetimepicker": "github:react-native-community/datetimepicker",
"@react-native-community/datetimepicker": "8.2.0",
"@react-native-community/netinfo": "^11.3.2",
"@react-native-firebase/analytics": "^21.0.0",
"@react-native-firebase/app": "^21.5.0",
Expand Down
6 changes: 6 additions & 0 deletions src/Routes/Public/DashboardStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Courses from '../../screens/Dashboard/Courses/Courses';
import ViewAllContent from '../../screens/Dashboard/ViewAllContent';
import CourseContentList from '../../screens/Dashboard/Courses/CourseContentList';
import UnitList from '../../screens/Dashboard/Courses/UnitList';
import CompleteProfileFormScreen from '../../screens/Profile/CompleteProfileFormScreen';

const Stack = createNativeStackNavigator();

Expand All @@ -28,6 +29,11 @@ const DashboardStack = ({ CopilotStopped, customProp = null }) => {
component={ViewAllContent}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
<Stack.Screen
name="CompleteProfileForm"
component={CompleteProfileFormScreen}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
</Stack.Navigator>
);
};
Expand Down
6 changes: 6 additions & 0 deletions src/Routes/SCPUser/SCPUserStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import FullAttendance from '../../screens/Dashboard/Calendar/FullAttendance';
import TimeTable from '../../screens/Dashboard/Calendar/TimeTable';
import PreviousClassMaterialFullView from '../../screens/Dashboard/Preference/SCPDashboard/PreviousClassMaterialFullView';
import SubjectDetails from '../../screens/Dashboard/Preference/SCPDashboard/SubjectDetails';
import CompleteProfileFormScreen from '../../screens/Profile/CompleteProfileFormScreen';

//for deep link
import CourseContentList from '@src/screens/Dashboard/Courses/CourseContentList';
Expand Down Expand Up @@ -58,6 +59,11 @@ const SCPUserStack = () => {
component={SubjectDetails}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
<Stack.Screen
name="CompleteProfileForm"
component={CompleteProfileFormScreen}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
{/* //for deep link course */}
<Stack.Screen
name="CourseContentList"
Expand Down
6 changes: 6 additions & 0 deletions src/Routes/Youthnet/YouthNetStack.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
import L1Courses from '@src/screens/YouthNet/L1Courses';
import CourseContentList from '@src/screens/Dashboard/Courses/CourseContentList';
import UnitList from '@src/screens/Dashboard/Courses/UnitList';
import CompleteProfileFormScreen from '@src/screens/Profile/CompleteProfileFormScreen';

const Stack = createNativeStackNavigator();

Expand All @@ -25,6 +26,11 @@ const YouthNetStack = () => {
component={UnitList}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
<Stack.Screen
name="CompleteProfileForm"
component={CompleteProfileFormScreen}
options={{ lazy: true }} // Lazily load LoadingScreen
/>
</Stack.Navigator>
);
};
Expand Down
110 changes: 110 additions & 0 deletions src/components/CompleteProfileBanner/CompleteProfileBanner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
import React, { useCallback, useState } from 'react';
import { StyleSheet, TouchableOpacity, View } from 'react-native';
import { useFocusEffect, useNavigation } from '@react-navigation/native';
import GlobalText from '@components/GlobalText/GlobalText';
import { useTranslation } from '@context/LanguageContext';
import globalStyles from '../../utils/Helper/Style';
import {
buildUserDetailsObject,
getDataFromStorage,
getMergedProfileSchema,
getMissingProfileFields,
getProfileCompletionSchema,
} from '../../utils/JsHelper/Helper';

const CompleteProfileBanner = () => {
const { t } = useTranslation();
const navigation = useNavigation();
const [banner, setBanner] = useState({
visible: false,
tenantId: null,
missingFields: [],
});

const checkProfileCompletion = useCallback(async () => {
try {
const tenantData = JSON.parse((await getDataFromStorage('tenantData')) || 'null');
const tenantId = tenantData?.[0]?.tenantId;
if (!tenantId) {
setBanner({ visible: false, tenantId: null, missingFields: [] });
return;
}

// Completeness is judged only on the current program's required fields
// (see getProfileCompletionSchema). The user's saved values are still read
// against the full merged schema so common-form labels resolve correctly.
const mergedSchema = await getMergedProfileSchema(tenantId);
const completionSchema = await getProfileCompletionSchema(tenantId);
const profileData = JSON.parse((await getDataFromStorage('profileData')) || 'null');
const userDetails = buildUserDetailsObject(profileData, mergedSchema);
const { missingFields, isComplete } = getMissingProfileFields(
completionSchema,
userDetails
);

setBanner({ visible: !isComplete, tenantId, missingFields });
} catch {
// Fail closed - don't nag the user if we couldn't determine completeness.
setBanner({ visible: false, tenantId: null, missingFields: [] });
}
}, []);

useFocusEffect(
useCallback(() => {
checkProfileCompletion();
}, [checkProfileCompletion])
);

if (!banner.visible) {
return null;
}

return (
<View style={styles.container}>
<GlobalText style={[globalStyles.text, styles.message]}>
{t('complete_profile_banner_message')}
</GlobalText>
<TouchableOpacity
style={styles.button}
onPress={() =>
navigation.navigate('CompleteProfileForm', {
tenantId: banner.tenantId,
missingFields: banner.missingFields,
})
}
>
<GlobalText style={styles.buttonText}>{t('complete_profile_button')}</GlobalText>
</TouchableOpacity>
</View>
);
};

const styles = StyleSheet.create({
container: {
backgroundColor: '#FCE7B8',
borderRadius: 16,
marginVertical: 15,
padding: 15,
},
message: {
marginBottom: 10,
},
button: {
alignSelf: 'center',
borderRadius: 20,
backgroundColor: '#F0A809',
paddingHorizontal: 14,
height: 36,
justifyContent: 'center',
alignItems: 'center',
},
buttonText: {
textAlign: 'center',
fontWeight: '700',
fontFamily: 'Roboto-Black',
fontSize: 13,
color: '#1F1B13',
},
});

export default CompleteProfileBanner;
15 changes: 7 additions & 8 deletions src/components/CustomRadioCard/RadioButton.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
StyleSheet,
Text,
View,
ScrollView,
TouchableOpacity,
Image,
Dimensions,
Expand Down Expand Up @@ -39,7 +38,7 @@ const RadioButton = ({ field, formData, handleValue, errors }) => {
</GlobalText>

<RadioGroup selectedIndex={selectedIndex} onChange={handlePress}>
<ScrollView>
<View>
<View
style={{
flexWrap: 'wrap',
Expand Down Expand Up @@ -80,21 +79,21 @@ const RadioButton = ({ field, formData, handleValue, errors }) => {
{errors[field.name] && (
<GlobalText style={styles.error}>{errors[field.name]}</GlobalText>
)}
</ScrollView>
</View>
</RadioGroup>
</>
);
};

const styles = StyleSheet.create({
card: {
backgroundColor: '#fff',
padding: 20,
marginVertical: 10,
marginHorizontal: 10,
backgroundColor: '#b51212',
padding:10,
marginVertical: 8,
marginHorizontal: 8,
borderRadius: 8,
elevation: 3,
width: '44%',
//width: '44%',
},
radioContainer: {
flexDirection: 'row',
Expand Down
97 changes: 46 additions & 51 deletions src/components/CustomTextField/CustomTextField.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,61 +33,59 @@ const CustomTextField = ({
};
return (
<View style={styles.container}>
<TextInput
<GlobalText
style={[
styles.input,
{
borderColor: errors[field.name] ? 'red' : '#DADADA',
backgroundColor: editable ? 'white' : '#F5F5F5',
color: editable ? 'black' : '#A0A0A0'
},
styles.text,
{ color: errors[field.name] ? 'red' : '#4D4639' },
]}
value={formData[field.name] || ''}
onChangeText={(text) => handleValue(field.name, text.trim())}
secureTextEntry={secureTextEntry}
autoCapitalize={autoCapitalize} // Disable auto-capitalization
keyboardType={keyboardType} // Opens numeric keyboard by default
editable={editable}
/>
{text && (
<TouchableOpacity
style={{
position: 'absolute',
right: 0,
marginRight: 30,
marginTop: 15,
alignSelf: 'center',
}}
onPress={() => handleCopyLink(formData[field.name])}
>
<Icon
name={showToast ? 'clipboard-check' : 'copy'}
color={showToast ? '#1A8825' : '#0D599E'}
size={25}
/>
</TouchableOpacity>
)}
<View style={styles.overlap}>
<GlobalText
>
{t(field.label.toLowerCase())}
{!field?.isRequired &&
!['guardian_name', 'guardian_relation'].includes(field.name) &&
`(${t('optional')})`}
</GlobalText>
<View style={styles.inputRow}>
<TextInput
style={[
styles.text,
{ color: errors[field.name] ? 'red' : '#4D4639' },
styles.input,
{
borderColor: errors[field.name] ? 'red' : '#DADADA',
backgroundColor: editable ? 'white' : '#F5F5F5',
color: editable ? 'black' : '#A0A0A0'
},
]}
>
{t(field.label.toLowerCase())}
{!field?.isRequired &&
!['guardian_name', 'guardian_relation'].includes(field.name) &&
`(${t('optional')})`}
</GlobalText>
value={formData[field.name] || ''}
onChangeText={(text) => handleValue(field.name, text.trim())}
secureTextEntry={secureTextEntry}
autoCapitalize={autoCapitalize} // Disable auto-capitalization
keyboardType={keyboardType} // Opens numeric keyboard by default
editable={editable}
/>
{text && (
<TouchableOpacity
style={{
position: 'absolute',
right: 0,
marginRight: 30,
top: 15,
}}
onPress={() => handleCopyLink(formData[field.name])}
>
<Icon
name={showToast ? 'clipboard-check' : 'copy'}
color={showToast ? '#1A8825' : '#0D599E'}
size={25}
/>
</TouchableOpacity>
)}
</View>

{errors[field.name] && (
<GlobalText
style={{
color: 'red',
alignSelf: 'flex-start',
marginBottom: 10,
marginTop: -20,
marginTop: 4,
fontFamily: 'Poppins-Regular',
}}
>
Expand Down Expand Up @@ -131,17 +129,14 @@ const styles = StyleSheet.create({
fontSize: 16,
fontFamily: 'Poppins-Regular',
},
overlap: {
top: -62,
left: 13,
// top: -76,
// left: -120,
backgroundColor: 'white',
inputRow: {
width: '100%',
},
text: {
color: '#4D4639',
paddingLeft: 2,
fontFamily: 'Poppins-Regular',
paddingRight: 2,
marginBottom: 6,
fontFamily: 'Poppins-Regular',
},
});
Loading