fixed some ui

This commit is contained in:
Abin Toppo
2026-03-26 15:07:08 +05:30
parent a98e0b6cdd
commit 585989eac2
24 changed files with 473 additions and 415 deletions
+3 -1
View File
@@ -1,5 +1,5 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { StyleSheet } from 'react-native'; import { LogBox, StyleSheet } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context'; import { SafeAreaProvider } from 'react-native-safe-area-context';
import { PaperProvider } from 'react-native-paper'; import { PaperProvider } from 'react-native-paper';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
@@ -18,6 +18,8 @@ import {
import { ThemeProvider } from './src/theme/ThemeProvider'; import { ThemeProvider } from './src/theme/ThemeProvider';
LogBox.ignoreAllLogs(true);
const queryClient = new QueryClient({ const queryClient = new QueryClient({
defaultOptions: { defaultOptions: {
queries: { queries: {
@@ -1,13 +1,13 @@
arguments= arguments=--init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.53.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\init\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.53.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\protobuf\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.53.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\kotlin\\init.gradle --init-script C\:\\Users\\Lincpay\\AppData\\Roaming\\Code\\User\\globalStorage\\redhat.java\\1.53.0\\config_win\\org.eclipse.osgi\\58\\0\\.cp\\gradle\\groovy\\init.gradle
auto.sync=false auto.sync=false
build.scans.enabled=false build.scans.enabled=false
connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER) connection.gradle.distribution=GRADLE_DISTRIBUTION(WRAPPER)
connection.project.dir= connection.project.dir=
eclipse.preferences.version=1 eclipse.preferences.version=1
gradle.user.home= gradle.user.home=
java.home= java.home=C\:/Program Files/Microsoft/jdk-17.0.14.7-hotspot
jvm.arguments= jvm.arguments=
offline.mode=false offline.mode=false
override.workspace.settings=false override.workspace.settings=true
show.console.view=false show.console.view=true
show.executions.view=false show.executions.view=true
+1
View File
@@ -81,6 +81,7 @@ apiClient.interceptors.request.use(
async (config) => { async (config) => {
try { try {
const credentials = await Keychain.getGenericPassword(); const credentials = await Keychain.getGenericPassword();
console.log(`[HOOK_API] Token`, credentials);
if (credentials) { if (credentials) {
config.headers.Authorization = `Bearer ${credentials.password}`; config.headers.Authorization = `Bearer ${credentials.password}`;
} }
+5 -4
View File
@@ -22,7 +22,7 @@ interface JoinedGroup {
currentParticipants: number; currentParticipants: number;
contributionAmount: number; contributionAmount: number;
cycleType: 'MONTHLY' | 'WEEKLY' | 'QUARTERLY' | 'CUSTOM'; cycleType: 'MONTHLY' | 'WEEKLY' | 'QUARTERLY' | 'CUSTOM';
status: 'ACTIVE' | 'PENDING' | 'COMPLETED'; status: 'ACTIVE' | 'PENDING' | 'COMPLETED'| 'APPROVED';
} }
interface JoinedGroupCardProps { interface JoinedGroupCardProps {
@@ -42,9 +42,10 @@ const formatDate = (dateStr: string | null): string => {
return `${dayNum} ${months[monthIndex]} ${year}`; return `${dayNum} ${months[monthIndex]} ${year}`;
}; };
export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item }) => { export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item }:any) => {
const theme = useTheme(); const theme = useTheme();
const navigation = useNavigation<any>(); const navigation = useNavigation<any>();
console.log("item",item)
const { data: payableRes } = useFetch<any>(`${CONTRIBUTE}${item.id}/calculate-contribution`); const { data: payableRes } = useFetch<any>(`${CONTRIBUTE}${item.id}/calculate-contribution`);
const payable = payableRes?.data; const payable = payableRes?.data;
@@ -101,8 +102,8 @@ export const JoinedGroupCard: FC<JoinedGroupCardProps> = ({ item }) => {
</Text> </Text>
</View> </View>
<View style={styles.infoItem}> <View style={styles.infoItem}>
<Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Type</Text> <Text style={[styles.infoLabel, { color: theme.colors.textMuted }]}>Duration</Text>
<Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>{item.cycleType}</Text> <Text style={[styles.infoValue, { color: theme.colors.textPrimary }]}>{`${item?.cycleType} (${item?.customCycleDays} days)`}</Text>
</View> </View>
</View> </View>
+1 -1
View File
@@ -74,7 +74,7 @@ export const MyGroupCard: React.FC<Props> = ({ item, onManage }) => {
<View style={styles.metaItem}> <View style={styles.metaItem}>
<LucideIcon name="cash-outline" size={14} color={theme.colors.textSecondary} /> <LucideIcon name="cash-outline" size={14} color={theme.colors.textSecondary} />
<Text style={[styles.cardMetaText, { color: theme.colors.textSecondary }]}> <Text style={[styles.cardMetaText, { color: theme.colors.textSecondary }]}>
₹{item.contributionAmount} UGX{item.contributionAmount}
</Text> </Text>
</View> </View>
<View style={styles.metaItem}> <View style={styles.metaItem}>
+3 -3
View File
@@ -65,14 +65,14 @@ const ContributionDetails: React.FC<ContributionProps> = ({
<View style={[styles.card, { backgroundColor: theme.colors.background, borderColor: theme.colors.border }]}> <View style={[styles.card, { backgroundColor: theme.colors.background, borderColor: theme.colors.border }]}>
<View style={styles.headerRow}> <View style={styles.headerRow}>
<LucideIcon name="wallet-outline" size={24} color={theme.colors.primary} /> <LucideIcon name="wallet-outline" size={24} color={theme.colors.primary} />
<Text style={[styles.amount, { color: theme.colors.textPrimary }]}>₹{data?.totalPayable || data?.baseAmount}</Text> <Text style={[styles.amount, { color: theme.colors.textPrimary }]}>UGX{data?.totalPayable || data?.baseAmount}</Text>
</View> </View>
<View style={styles.infoGrid}> <View style={styles.infoGrid}>
<InfoItem label="Base Amount" value={`₹${data?.baseAmount}`} /> <InfoItem label="Base Amount" value={`UGX${data?.baseAmount}`} />
<InfoItem label="Due Date" value={data?.dueDate} /> <InfoItem label="Due Date" value={data?.dueDate} />
<InfoItem label="Cycle End" value={data?.cycleEnd} /> <InfoItem label="Cycle End" value={data?.cycleEnd} />
<InfoItem label="Late Fee" value={`₹${data?.effectiveLateFee || 0}`} /> <InfoItem label="Late Fee" value={`UGX${data?.effectiveLateFee || 0}`} />
</View> </View>
</View> </View>
+3 -3
View File
@@ -217,7 +217,7 @@ export default function GroupCreationForm() {
<Section title="Financial Details"> <Section title="Financial Details">
<CustomInput <CustomInput
label="Contribution Amount (₹)" label="Contribution Amount (UGX)"
placeholder="e.g., 5000.00" placeholder="e.g., 5000.00"
value={form.contributionAmount} value={form.contributionAmount}
onChangeText={(val) => handleChange('contributionAmount', val)} onChangeText={(val) => handleChange('contributionAmount', val)}
@@ -226,7 +226,7 @@ export default function GroupCreationForm() {
error={errors.contributionAmount} error={errors.contributionAmount}
/> />
<CustomInput <CustomInput
label="Entry Fee (₹)" label="Entry Fee (UGX)"
placeholder="e.g., 0 for free" placeholder="e.g., 0 for free"
value={form.entryFee} value={form.entryFee}
onChangeText={(val) => handleChange('entryFee', val)} onChangeText={(val) => handleChange('entryFee', val)}
@@ -272,7 +272,7 @@ export default function GroupCreationForm() {
/> />
</SettingRow> </SettingRow>
<CustomInput <CustomInput
label="Fee Amount (₹)" label="Late Fee Amount (UGX)"
placeholder="e.g., 50" placeholder="e.g., 50"
value={form.lateFeeValue} value={form.lateFeeValue}
onChangeText={(val) => handleChange('lateFeeValue', val)} onChangeText={(val) => handleChange('lateFeeValue', val)}
+3 -3
View File
@@ -189,8 +189,8 @@ export default function GroupReview({ route, navigation }: any) {
</TimelineSection> </TimelineSection>
<TimelineSection icon="wallet-outline" title="Financials"> <TimelineSection icon="wallet-outline" title="Financials">
{renderRow('Contribution', `₹${form.contributionAmount}`)} {renderRow('Contribution', `UGX${form.contributionAmount}`)}
{renderRow('Entry Fee', `₹${form.entryFee}`)} {renderRow('Entry Fee', `UGX${form.entryFee}`)}
{renderRow('Cycle', getCycleLabel(form.cycleType, form.customCycleDays))} {renderRow('Cycle', getCycleLabel(form.cycleType, form.customCycleDays))}
{form.cycleType === 'CUSTOM' && !['1', '7'].includes(String(form.customCycleDays)) && {form.cycleType === 'CUSTOM' && !['1', '7'].includes(String(form.customCycleDays)) &&
renderRow('Cycle Days', `${form.customCycleDays} days`)} renderRow('Cycle Days', `${form.customCycleDays} days`)}
@@ -198,7 +198,7 @@ export default function GroupReview({ route, navigation }: any) {
<TimelineSection icon="alert-circle-outline" title="Policies"> <TimelineSection icon="alert-circle-outline" title="Policies">
{renderRow('Fee Mode', form.lateFeeMode)} {renderRow('Fee Mode', form.lateFeeMode)}
{renderRow('Fee Value', `₹${form.lateFeeValue}`)} {renderRow('Fee Value', `UGX${form.lateFeeValue}`)}
</TimelineSection> </TimelineSection>
<CustomButton <CustomButton
+1 -1
View File
@@ -119,7 +119,7 @@ const GroupCard = ({
<View style={styles.metaItem}> <View style={styles.metaItem}>
<LucideIcon name="ticket-outline" size={14} color={theme.colors.textMuted} /> <LucideIcon name="ticket-outline" size={14} color={theme.colors.textMuted} />
<Text style={[styles.metaText, { color: theme.colors.textSecondary }]}> <Text style={[styles.metaText, { color: theme.colors.textSecondary }]}>
₹{item.entryFee} UGX{item.entryFee}
</Text> </Text>
</View> </View>
) : null} ) : null}
+3 -3
View File
@@ -139,16 +139,16 @@ const InviteScreen: React.FC<InviteScreenProps> = ({ route, navigation }) => {
<View style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}> <View style={[styles.card, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>
<Text style={[styles.cardTitle, { color: theme.colors.textPrimary }]}>Circle Overview</Text> <Text style={[styles.cardTitle, { color: theme.colors.textPrimary }]}>Circle Overview</Text>
<InfoRow label="Organizer" value={organizerName} /> <InfoRow label="Organizer" value={organizerName} />
<InfoRow label="Contribution" value={`₹${groupDetails.contributionAmount.toLocaleString('en-IN')}`} /> <InfoRow label="Contribution" value={`UGX${groupDetails.contributionAmount.toLocaleString('en-IN')}`} />
<InfoRow <InfoRow
label="Frequency" label="Frequency"
value={groupDetails.cycleType === 'CUSTOM' ? `${groupDetails.customCycleDays} Day(s)` : groupDetails.cycleType} value={groupDetails.cycleType === 'CUSTOM' ? `${groupDetails.customCycleDays} Day(s)` : groupDetails.cycleType}
/> />
<InfoRow label="Min/Max Participants" value={`${groupDetails.minParticipants} / ${groupDetails.maxParticipants}`} /> <InfoRow label="Min/Max Participants" value={`${groupDetails.minParticipants} / ${groupDetails.maxParticipants}`} />
<InfoRow label="Entry Fee" value={`₹${groupDetails.entryFee}`} /> <InfoRow label="Entry Fee" value={`UGX${groupDetails.entryFee}`} />
<InfoRow <InfoRow
label="Late Payment Charge" label="Late Payment Charge"
value={`₹${groupDetails.latePaymentCharge} (${groupDetails.lateFeeMode || 'DAILY'})`} value={`UGX${groupDetails.latePaymentCharge} (${groupDetails.lateFeeMode || 'DAILY'})`}
/> />
<InfoRow label="Withdrawal Method" value={groupDetails.withdrawalMethod || 'RANDOM'} /> <InfoRow label="Withdrawal Method" value={groupDetails.withdrawalMethod || 'RANDOM'} />
<InfoRow label="Status" value={groupDetails.status} /> <InfoRow label="Status" value={groupDetails.status} />
+3 -2
View File
@@ -43,8 +43,8 @@ export default function LoginScreen() {
}, [fadeAnim, slideAnim]); }, [fadeAnim, slideAnim]);
const { refetch: sendOtp, isFetching: loading } = useFetch<any>( const { refetch: sendOtp, isFetching: loading } = useFetch<any>(
LOGIN_URL, LOGIN_URL + mobile,
{ mobile }, {},
{ {
enabled: false, enabled: false,
retry: false, retry: false,
@@ -100,6 +100,7 @@ export default function LoginScreen() {
value={mobile} value={mobile}
onChangeText={(text) => { onChangeText={(text) => {
setMobile(text.replace(/\D/g, '')); setMobile(text.replace(/\D/g, ''));
if (error) setError(''); if (error) setError('');
}} }}
keyboardType="phone-pad" keyboardType="phone-pad"
+26 -1
View File
@@ -97,6 +97,30 @@ export default function OtpVerificationScreen() {
}); });
const handleTextChange = (text: string, index: number) => { const handleTextChange = (text: string, index: number) => {
// If text length > 1, it's likely a paste operation
if (text.length > 1) {
const pastedCode = text.trim().slice(0, CELL_COUNT).split('');
const newCode = [...code];
pastedCode.forEach((char, i) => {
if (i < CELL_COUNT) {
newCode[i] = char;
}
});
setCode(newCode);
// Focus the last filled cell or the next empty one
const focusIndex = Math.min(pastedCode.length, CELL_COUNT - 1);
inputs.current[focusIndex]?.focus();
// Trigger verification if OTP complete
if (newCode.every((cell) => cell !== '')) {
verifyOtp({ mobile, otp: newCode.join('') });
}
return;
}
const newCode = [...code]; const newCode = [...code];
newCode[index] = text.slice(-1); newCode[index] = text.slice(-1);
setCode(newCode); setCode(newCode);
@@ -153,13 +177,14 @@ export default function OtpVerificationScreen() {
}, },
]} ]}
keyboardType="number-pad" keyboardType="number-pad"
maxLength={1} maxLength={CELL_COUNT}
value={digit} value={digit}
placeholder="0" placeholder="0"
placeholderTextColor={theme.colors.textMuted} placeholderTextColor={theme.colors.textMuted}
onChangeText={(text) => handleTextChange(text, index)} onChangeText={(text) => handleTextChange(text, index)}
onKeyPress={(e) => handleKeyPress(e, index)} onKeyPress={(e) => handleKeyPress(e, index)}
selectionColor={theme.colors.primary} selectionColor={theme.colors.primary}
textContentType="oneTimeCode"
/> />
))} ))}
</View> </View>
+1 -1
View File
@@ -72,7 +72,7 @@ const GroupCard = ({ item }: { item: any }) => {
{item.baseAmount && ( {item.baseAmount && (
<View style={styles.detailRow}> <View style={styles.detailRow}>
<Text style={[styles.detailLabel, { color: theme.colors.textSecondary }]}>Contribution</Text> <Text style={[styles.detailLabel, { color: theme.colors.textSecondary }]}>Contribution</Text>
<Text style={[styles.detailValue, { color: theme.colors.textPrimary }]}>₹{item.baseAmount}</Text> <Text style={[styles.detailValue, { color: theme.colors.textPrimary }]}>UGX{item.baseAmount}</Text>
</View> </View>
)} )}
</View> </View>
+4 -4
View File
@@ -117,7 +117,7 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
<View style={[styles.dueBar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }]}> <View style={[styles.dueBar, { backgroundColor: theme.colors.surface, borderTopColor: theme.colors.border }]}>
<View> <View>
<Text style={[styles.dueBarLabel, { color: theme.colors.textSecondary }]}>Total Outstanding</Text> <Text style={[styles.dueBarLabel, { color: theme.colors.textSecondary }]}>Total Outstanding</Text>
<Text style={[styles.dueBarValue, { color: theme.colors.textPrimary }]}>₹{totalDue}</Text> <Text style={[styles.dueBarValue, { color: theme.colors.textPrimary }]}>UGX{totalDue}</Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
style={[styles.payButton, { backgroundColor: theme.colors.primary }]} style={[styles.payButton, { backgroundColor: theme.colors.primary }]}
@@ -201,17 +201,17 @@ const UserProfileStatus: React.FC<UserProfileStatusProps> = ({
<View style={styles.groupInfo}> <View style={styles.groupInfo}>
<Text style={[styles.groupName, { color: theme.colors.textPrimary }]}>{group.groupName}</Text> <Text style={[styles.groupName, { color: theme.colors.textPrimary }]}>{group.groupName}</Text>
<Text style={[styles.groupSub, { color: theme.colors.textSecondary }]}> <Text style={[styles.groupSub, { color: theme.colors.textSecondary }]}>
Amount: ₹{group.baseAmount} {group.lateFee > 0 ? `+ ₹${group.lateFee} fee` : ''} Amount: UGX{group.baseAmount} {group.lateFee > 0 ? `+ UGX${group.lateFee} fee` : ''}
</Text> </Text>
</View> </View>
<Text style={[styles.groupAmount, { color: theme.colors.textPrimary }]}>₹{group.totalDue}</Text> <Text style={[styles.groupAmount, { color: theme.colors.textPrimary }]}>UGX{group.totalDue}</Text>
</TouchableOpacity> </TouchableOpacity>
))} ))}
</ScrollView> </ScrollView>
<View style={[styles.modalFooter, { borderTopColor: theme.colors.border }]}> <View style={[styles.modalFooter, { borderTopColor: theme.colors.border }]}>
<View> <View>
<Text style={[styles.totalLabel, { color: theme.colors.textSecondary }]}>Payable</Text> <Text style={[styles.totalLabel, { color: theme.colors.textSecondary }]}>Payable</Text>
<Text style={[styles.totalValue, { color: theme.colors.textPrimary }]}>₹{totalDueSelected}</Text> <Text style={[styles.totalValue, { color: theme.colors.textPrimary }]}>UGX{totalDueSelected}</Text>
</View> </View>
<TouchableOpacity <TouchableOpacity
style={[styles.confirmButton, { backgroundColor: theme.colors.primary, opacity: submitting ? 0.7 : 1 }]} style={[styles.confirmButton, { backgroundColor: theme.colors.primary, opacity: submitting ? 0.7 : 1 }]}
+2 -2
View File
@@ -186,7 +186,7 @@ const GroupDetails = () => {
<View> <View>
<Text style={[styles.cardLabel, { fontFamily: FontsFamily.nunitoSemiBold }]}>Total Balance</Text> <Text style={[styles.cardLabel, { fontFamily: FontsFamily.nunitoSemiBold }]}>Total Balance</Text>
<Text style={[styles.cardBalance, { fontFamily: FontsFamily.nunitoExtraBold }]}> <Text style={[styles.cardBalance, { fontFamily: FontsFamily.nunitoExtraBold }]}>
₹{cycleDashboardData?.contributionAmount * memberData.length || group.goal} UGX{cycleDashboardData?.contributionAmount * memberData.length || group.goal}
</Text> </Text>
</View> </View>
<View style={styles.iconContainer}> <View style={styles.iconContainer}>
@@ -248,7 +248,7 @@ const GroupDetails = () => {
<LucideIcon name="cash" size={18} color={theme.colors.success} /> <LucideIcon name="cash" size={18} color={theme.colors.success} />
</View> </View>
<View> <View>
<Text style={[styles.miniStatValue, { color: theme.colors.textPrimary, fontFamily: FontsFamily.nunitoBold }]}>₹{cycleDashboardData?.contributionAmount || group.collected}</Text> <Text style={[styles.miniStatValue, { color: theme.colors.textPrimary, fontFamily: FontsFamily.nunitoBold }]}>UGX{cycleDashboardData?.contributionAmount || group.collected}</Text>
<Text style={[styles.miniStatLabel, { color: theme.colors.textMuted, fontFamily: FontsFamily.nunitoRegular }]}>Per Head</Text> <Text style={[styles.miniStatLabel, { color: theme.colors.textMuted, fontFamily: FontsFamily.nunitoRegular }]}>Per Head</Text>
</View> </View>
</View> </View>
+12 -12
View File
@@ -425,7 +425,7 @@ const EditGroupScreen: React.FC = () => {
</View> </View>
<TouchableOpacity onPress={pickImage} style={styles.cameraBtn}> <TouchableOpacity onPress={pickImage} style={styles.cameraBtn}>
<LucideIcon name="camera" size={20} color={theme.colors.primary} /> <LucideIcon name="camera" size={20} color={theme.colors.black} />
</TouchableOpacity> </TouchableOpacity>
{selectedImage && ( {selectedImage && (
@@ -452,7 +452,7 @@ const EditGroupScreen: React.FC = () => {
</View> </View>
<View style={styles.statDivider} /> <View style={styles.statDivider} />
<View style={styles.statBox}> <View style={styles.statBox}>
<Text style={styles.statValue}>₹{groupDetails.contributionAmount}</Text> <Text style={styles.statValue}>UGX{groupDetails.contributionAmount}</Text>
<Text style={styles.statLabel}>Contribution</Text> <Text style={styles.statLabel}>Contribution</Text>
</View> </View>
<View style={styles.statDivider} /> <View style={styles.statDivider} />
@@ -474,7 +474,7 @@ const EditGroupScreen: React.FC = () => {
const getDisplayValue = (field: string) => { const getDisplayValue = (field: string) => {
const val = groupDetails[field as keyof typeof groupDetails]; const val = groupDetails[field as keyof typeof groupDetails];
if (field.includes('Amount') || field.includes('Fee') || field.includes('Charge') || field.includes('Value')) return `₹${val ?? 0}`; if (field.includes('Amount') || field.includes('Fee') || field.includes('Charge') || field.includes('Value')) return `UGX${val ?? 0}`;
return String(val ?? '-'); return String(val ?? '-');
}; };
@@ -592,9 +592,9 @@ const EditGroupScreen: React.FC = () => {
<ActivityIndicator color="#fff" /> <ActivityIndicator color="#fff" />
) : ( ) : (
<> <>
<LucideIcon name="power" size={24} color="#fff" style={{ marginRight: 8 }} /> <LucideIcon name="power" size={24} color={ theme.colors.reverseText} style={{ marginRight: 8 }} />
<Text style={styles.floatingBtnText}>Activate Group</Text> <Text style={styles.floatingBtnText}>Activate Group</Text>
<LucideIcon name="arrow-forward" size={20} color="#fff" style={{ marginLeft: 8, opacity: 0.8 }} /> {/* <LucideIcon name="arrow-forward" size={20} color={ theme.colors.reverseText} style={{ marginLeft: 8, opacity: 0.8 }} /> */}
</> </>
)} )}
</TouchableOpacity> </TouchableOpacity>
@@ -682,8 +682,8 @@ const getStyles = (theme: any) => StyleSheet.create({
descContainer: { backgroundColor: 'rgba(255,255,255,0.15)', padding: 10, borderRadius: 8, borderLeftWidth: 3, borderLeftColor: '#fff', marginBottom: 12 }, descContainer: { backgroundColor: 'rgba(255,255,255,0.15)', padding: 10, borderRadius: 8, borderLeftWidth: 3, borderLeftColor: '#fff', marginBottom: 12 },
descText: { color: '#eee', fontSize: 14, fontFamily: FontsFamily.nunitoRegular }, descText: { color: '#eee', fontSize: 14, fontFamily: FontsFamily.nunitoRegular },
badgesRow: { flexDirection: 'row', gap: 10 }, badgesRow: { flexDirection: 'row', gap: 10 },
badge: { backgroundColor: 'rgba(0,0,0,0.5)', paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 }, badge: { backgroundColor: theme.colors.background, paddingHorizontal: 10, paddingVertical: 4, borderRadius: 12 },
badgeText: { color: '#fff', fontSize: 12, fontWeight: 'bold' }, badgeText: { color: theme.colors.textPrimary, fontSize: 12, fontWeight: 'bold' },
cameraBtn: { position: 'absolute', bottom: -20, right: 30, width: 44, height: 44, borderRadius: 22, backgroundColor: '#fff', elevation: 4, justifyContent: 'center', alignItems: 'center' }, cameraBtn: { position: 'absolute', bottom: -20, right: 30, width: 44, height: 44, borderRadius: 22, backgroundColor: '#fff', elevation: 4, justifyContent: 'center', alignItems: 'center' },
uploadFloatBtn: { position: 'absolute', bottom: 40, right: 30, width: 50, height: 50, borderRadius: 25, elevation: 5, justifyContent: 'center', alignItems: 'center' }, uploadFloatBtn: { position: 'absolute', bottom: 40, right: 30, width: 50, height: 50, borderRadius: 25, elevation: 5, justifyContent: 'center', alignItems: 'center' },
uploadFloatWrapper: { position: 'absolute', bottom: 40, right: 30, alignItems: 'center' }, uploadFloatWrapper: { position: 'absolute', bottom: 40, right: 30, alignItems: 'center' },
@@ -792,7 +792,7 @@ const getStyles = (theme: any) => StyleSheet.create({
borderBottomColor: '#334155', borderBottomColor: '#334155',
}, },
floatingBtnText: { floatingBtnText: {
color: '#fff', color: theme.colors.reverseText,
fontSize: 18, fontSize: 18,
fontFamily: FontsFamily.nunitoBold, fontFamily: FontsFamily.nunitoBold,
letterSpacing: 0.5, letterSpacing: 0.5,
@@ -800,15 +800,15 @@ const getStyles = (theme: any) => StyleSheet.create({
modalContent: { flex: 1 }, modalContent: { flex: 1 },
modalFieldContainer: { marginBottom: 16 }, modalFieldContainer: { marginBottom: 16 },
modalLabel: { fontSize: 14, color: theme.colors.textSecondary, marginBottom: 8, fontFamily: FontsFamily.nunitoBold }, modalLabel: { fontSize: 14, color: theme.colors.textSecondary, marginBottom: 8, fontFamily: FontsFamily.nunitoBold },
modalInput: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 12, padding: 14, fontSize: 16, color: theme.colors.textPrimary, backgroundColor: '#f9f9f9', fontFamily: FontsFamily.nunitoMedium }, modalInput: { borderWidth: 1, borderColor: '#e0e0e0', borderRadius: 12, padding: 14, fontSize: 16, color: theme.colors.black, backgroundColor: '#f9f9f9', fontFamily: FontsFamily.nunitoMedium },
chipContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 }, chipContainer: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
chip: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, backgroundColor: '#f0f0f0', borderWidth: 1, borderColor: '#e0e0e0' }, chip: { paddingHorizontal: 16, paddingVertical: 10, borderRadius: 20, backgroundColor: '#f0f0f0', borderWidth: 1, borderColor: '#e0e0e0' },
activeChip: { backgroundColor: theme.colors.primary, borderColor: theme.colors.primary }, activeChip: { backgroundColor: theme.colors.primary, borderColor: theme.colors.primary },
chipText: { fontSize: 14, fontFamily: FontsFamily.nunitoMedium, color: theme.colors.textSecondary }, chipText: { fontSize: 14, fontFamily: FontsFamily.nunitoMedium, color: theme.colors.black },
activeChipText: { color: theme.colors.white, fontFamily: FontsFamily.nunitoBold }, activeChipText: { color: theme.colors.reverseText, fontFamily: FontsFamily.nunitoBold },
modalFooter: { borderTopWidth: 1, borderTopColor: '#eee', paddingTop: 16 }, modalFooter: { borderTopWidth: 1, borderTopColor: '#eee', paddingTop: 16 },
saveBtn: { backgroundColor: theme.colors.primary, padding: 16, borderRadius: 12, alignItems: 'center' }, saveBtn: { backgroundColor: theme.colors.primary, padding: 16, borderRadius: 12, alignItems: 'center' },
saveBtnText: { color: theme.colors.white, fontSize: 16, fontFamily: FontsFamily.nunitoBold } saveBtnText: { color: theme.colors.reverseText, fontSize: 16, fontFamily: FontsFamily.nunitoBold }
}); });
export default EditGroupScreen; export default EditGroupScreen;
@@ -52,6 +52,7 @@ const JOINED_GROUP_STATUS_FILTERS: GroupStatusFilter[] = [
'ACTIVE', 'ACTIVE',
'DELAY', 'DELAY',
'COMPLETED', 'COMPLETED',
'APPROVED'
]; ];
const getListFromResponse = <T,>(response: any): T[] => { const getListFromResponse = <T,>(response: any): T[] => {
+7 -7
View File
@@ -133,7 +133,7 @@ const Payments: React.FC = () => {
} }
if (currentParticipant && amountToWaive > currentParticipant.lateFee) { if (currentParticipant && amountToWaive > currentParticipant.lateFee) {
ToastAndroid.show(`Waiver cannot exceed ₹${currentParticipant.lateFee}`, ToastAndroid.SHORT); ToastAndroid.show(`Waiver cannot exceed UGX${currentParticipant.lateFee}`, ToastAndroid.SHORT);
return; return;
} }
@@ -174,13 +174,13 @@ const Payments: React.FC = () => {
{participant.status} {participant.status}
</Text> </Text>
<Text style={[styles.amountText, { color: theme.colors.textPrimary }]}> <Text style={[styles.amountText, { color: theme.colors.textPrimary }]}>
₹{participant.status === 'SUCCESS' ? participant.totalPaidAmount : participant.expectedAmount} UGX{participant.status === 'SUCCESS' ? participant.totalPaidAmount : participant.expectedAmount}
</Text> </Text>
{participant.lateFee > 0 && ( {participant.lateFee > 0 && (
<Text style={[styles.lateFeeText, { color: theme.colors.error }]}>Late Fee: ₹{participant.lateFee}</Text> <Text style={[styles.lateFeeText, { color: theme.colors.error }]}>Late Fee: UGX{participant.lateFee}</Text>
)} )}
{participant.lateFeeWaived && ( {participant.lateFeeWaived && (
<Text style={[styles.waivedText, { color: theme.colors.success }]}>Waived: ₹{participant.waivedAmount}</Text> <Text style={[styles.waivedText, { color: theme.colors.success }]}>Waived: UGX{participant.waivedAmount}</Text>
)} )}
</View> </View>
@@ -212,7 +212,7 @@ const Payments: React.FC = () => {
confirmText={isWaiving ? 'Applying...' : 'Apply Waiver'} confirmText={isWaiving ? 'Applying...' : 'Apply Waiver'}
> >
<Text style={[styles.modalSubtitle, { color: theme.colors.textSecondary }]}> <Text style={[styles.modalSubtitle, { color: theme.colors.textSecondary }]}>
For {currentParticipant?.firstName}. Current late fee: ₹{currentParticipant?.lateFee} For {currentParticipant?.firstName}. Current late fee: UGX{currentParticipant?.lateFee}
</Text> </Text>
<TextInput <TextInput
style={[ style={[
@@ -282,14 +282,14 @@ const Payments: React.FC = () => {
<View style={styles.summaryBox}> <View style={styles.summaryBox}>
<Text style={[styles.summaryLabel, { color: theme.colors.textSecondary }]}>Contribution</Text> <Text style={[styles.summaryLabel, { color: theme.colors.textSecondary }]}>Contribution</Text>
<Text style={[styles.summaryValue, { color: theme.colors.textPrimary }]}> <Text style={[styles.summaryValue, { color: theme.colors.textPrimary }]}>
₹{paymentDetails.contributionAmount} UGX{paymentDetails.contributionAmount}
</Text> </Text>
</View> </View>
<View style={styles.divider} /> <View style={styles.divider} />
<View style={styles.summaryBox}> <View style={styles.summaryBox}>
<Text style={[styles.summaryLabel, { color: theme.colors.textSecondary }]}>Total Amount</Text> <Text style={[styles.summaryLabel, { color: theme.colors.textSecondary }]}>Total Amount</Text>
<Text style={[styles.summaryValue, { color: theme.colors.primary }]}> <Text style={[styles.summaryValue, { color: theme.colors.primary }]}>
₹{paymentDetails.totalDueWithLateFee} UGX{paymentDetails.totalDueWithLateFee}
</Text> </Text>
</View> </View>
</View> </View>
+332 -352
View File
@@ -1,4 +1,4 @@
// import React from 'react'; import React from 'react';
import { import {
View, View,
Text, Text,
@@ -6,274 +6,265 @@ import {
FlatList, FlatList,
StyleSheet, StyleSheet,
} from 'react-native'; } from 'react-native';
import { useDispatch, useSelector } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { import { removeNotification } from '../../redux/notificationSlice';
removeNotification,
} from '../../redux/notificationSlice';
import LinearGradient from 'react-native-linear-gradient'; import LinearGradient from 'react-native-linear-gradient';
import LucideIcon from '../../components/common/LucideIcon'; import LucideIcon from '../../components/common/LucideIcon';
import AppView from '../../components/AppView'; import AppView from '../../components/AppView';
import Header from '../../components/common/Header'; import Header from '../../components/common/Header';
import { useNotificationHandler } from '../hook/useNotificationHandler'; import { useNotificationHandler } from '../hook/useNotificationHandler';
import colors from '../../components/colors'; import { useTheme } from '../../theme/ThemeProvider';
type FilterType = 'all' | 'JOIN_REQUEST' | 'CONTRIBUTION' | 'CONTRIBUTION_ACK' | import { Swipeable } from 'react-native-gesture-handler';
'CYCLE_COMPLETED' | 'WITHDRAWAL' | 'WITHDRAWAL_NOTIFY' | import Animated, {
'CYCLE_WAIVER' | 'WAIVER_RECEIVED' | 'USER_SELECTED' | useSharedValue,
'GROUP_STARTED' | 'JOIN_APPROVED' | 'JOIN_VIA_INVITE'; useAnimatedStyle,
withSpring,
} from 'react-native-reanimated';
type FilterType =
| 'all'
| 'JOIN_REQUEST'
| 'CONTRIBUTION'
| 'CONTRIBUTION_ACK'
| 'CYCLE_COMPLETED'
| 'WITHDRAWAL'
| 'WITHDRAWAL_NOTIFY'
| 'CYCLE_WAIVER'
| 'WAIVER_RECEIVED'
| 'USER_SELECTED'
| 'GROUP_STARTED'
| 'JOIN_APPROVED'
| 'JOIN_VIA_INVITE';
const Notification = () => { const Notification = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
const { handleMarkAsReadAndNavigate } = useNotificationHandler(); const { handleMarkAsReadAndNavigate } = useNotificationHandler();
const { theme } = useTheme();
const notifications = useSelector( const notifications = useSelector(
(state: any) => state.notifications.notifications (state: any) => state.notifications.notifications ?? []
); );
console.log("notifications ==>", notifications);
const handleDelete = (messageId: string) => { const handleDelete = (messageId: string) => {
dispatch(removeNotification(messageId)); dispatch(removeNotification(messageId));
}; };
const handleMarkAsRead = (messageId: string, screen: string, groupId: string, cycleNumber: string, contributionId: string) => { const handleMarkAsRead = (
console.log("screen type : ", screen); messageId: string,
handleMarkAsReadAndNavigate( screen: string,
messageId, groupId: string,
screen, cycleNumber: string,
{ contributionId: string
groupId: groupId, ) => {
cycleNumber, handleMarkAsReadAndNavigate(messageId, screen, {
contributionId, groupId,
actionTab3: 'Your Requests', cycleNumber,
actionTab1: 'Joined Groups', contributionId,
GroupDetails: { actionTab3: 'Your Requests',
item: {} actionTab1: 'Joined Groups',
}, GroupDetails: { item: {} },
} });
);
}; };
const formatTime = (timestamp: number) => { const formatTime = (timestamp: number) => {
const date = new Date(timestamp); const date = new Date(timestamp);
const now = new Date(); const now = new Date();
const diffInHours = (now.getTime() - date.getTime()) / (1000 * 60 * 60); const diff = (now.getTime() - date.getTime()) / (1000 * 60 * 60);
if (diffInHours < 1) { if (diff < 1) return 'Just now';
return 'Just now'; if (diff < 24) return `${Math.floor(diff)}h ago`;
} else if (diffInHours < 24) { return date.toLocaleDateString();
return `${Math.floor(diffInHours)}h ago`;
} else {
return date.toLocaleDateString();
}
}; };
const getNotificationIcon = (msgType: string) => { const getNotificationIcon = (type: string) => {
switch (msgType) { const map: any = {
case 'JOIN_REQUEST': JOIN_REQUEST: 'person-add-outline',
return 'person-add-outline'; CONTRIBUTION: 'cash-outline',
case 'CONTRIBUTION': CONTRIBUTION_ACK: 'checkmark-done-outline',
return 'cash-outline'; CYCLE_COMPLETED: 'trophy-outline',
case 'CONTRIBUTION_ACK': WITHDRAWAL: 'card-outline',
return 'checkmark-done-outline'; WITHDRAWAL_NOTIFY: 'notifications-outline',
case 'CYCLE_COMPLETED': GROUP_STARTED: 'rocket-outline',
return 'trophy-outline'; };
case 'WITHDRAWAL': return map[type] || 'notifications-outline';
return 'card-outline';
case 'WITHDRAWAL_NOTIFY':
return 'notifications-outline';
case 'CYCLE_WAIVER':
return 'document-text-outline';
case 'WAIVER_RECEIVED':
return 'download-outline';
case 'USER_SELECTED':
return 'people-outline';
case 'GROUP_STARTED':
return 'rocket-outline';
case 'JOIN_APPROVED':
return 'checkmark-circle-outline';
case 'JOIN_VIA_INVITE':
return 'gift-outline';
default:
return 'notifications-outline';
}
}; };
const getNotificationColor = (type: string) => {
const getNotificationColor = (msgType: string) => { const map: any = {
switch (msgType) { JOIN_REQUEST: '#FFB6C1',
case 'JOIN_REQUEST': CONTRIBUTION: '#98FB98',
return '#FFB6C1'; // Pastel Pink CONTRIBUTION_ACK: '#ADD8E6',
case 'CONTRIBUTION': CYCLE_COMPLETED: '#D8BFD8',
return '#98FB98'; // Pastel Green WITHDRAWAL: '#FFD700',
case 'CONTRIBUTION_ACK': WITHDRAWAL_NOTIFY: '#FFA07A',
return '#ADD8E6'; // Pastel Blue };
case 'CYCLE_COMPLETED': return map[type] || '#D3D3D3';
return '#D8BFD8'; // Pastel Purple
case 'WITHDRAWAL':
return '#FFD700'; // Pastel Yellow
case 'WITHDRAWAL_NOTIFY':
return '#FFA07A'; // Pastel Orange
case 'CYCLE_WAIVER':
return '#B0C4DE'; // Pastel Steel Blue
case 'WAIVER_RECEIVED':
return '#AFEEEE'; // Pastel Turquoise
case 'USER_SELECTED':
return '#DDA0DD'; // Pastel Plum
case 'GROUP_STARTED':
return '#87CEFA'; // Pastel Sky Blue
case 'JOIN_APPROVED':
return '#98FB98'; // Pastel Green
case 'JOIN_VIA_INVITE':
return '#FFFACD'; // Pastel Lemon
default:
return '#D3D3D3'; // Pastel Gray
}
}; };
const getFilterDisplayName = (filter: FilterType) => { const getFilterDisplayName = (filter: FilterType) => {
switch (filter) { return filter.replace(/_/g, ' ');
case 'JOIN_REQUEST':
return 'Requests';
case 'CONTRIBUTION':
return 'Contributions';
case 'CONTRIBUTION_ACK':
return 'Contributions Ack';
case 'CYCLE_COMPLETED':
return 'Cycle Completed';
case 'WITHDRAWAL':
return 'Withdrawals';
case 'WITHDRAWAL_NOTIFY':
return 'Withdrawal Notify';
case 'CYCLE_WAIVER':
return 'Waivers';
case 'WAIVER_RECEIVED':
return 'Waiver Received';
case 'USER_SELECTED':
return 'User Selected';
case 'GROUP_STARTED':
return 'Group Started';
case 'JOIN_APPROVED':
return 'Join Approved';
case 'JOIN_VIA_INVITE':
return 'Invites';
default:
return 'All';
}
}; };
const unreadCount = notifications.filter((item: any) => !item.read).length; const unreadCount = notifications.filter((i: any) => !i.read).length;
// 🔥 Swipe Delete UI
const renderRightActions = (dragX: any, messageId: string) => {
const scale = dragX.interpolate({
inputRange: [-100, 0],
outputRange: [1, 0],
extrapolate: 'clamp',
});
return (
<TouchableOpacity
onPress={() => handleDelete(messageId)}
style={styles(theme).deleteSwipe}
>
<Animated.View style={{ transform: [{ scale }] }}>
<LucideIcon name="trash-outline" size={22} color="#fff" />
<Text style={styles(theme).deleteText}>Delete</Text>
</Animated.View>
</TouchableOpacity>
);
};
const renderItem = ({ item }: any) => {
const scale = useSharedValue(1);
const animatedStyle = useAnimatedStyle(() => ({
transform: [{ scale: scale.value }],
}));
return (
<Swipeable
renderRightActions={(_, dragX) =>
renderRightActions(dragX, item.messageId)
}
>
<Animated.View style={animatedStyle}>
<TouchableOpacity
activeOpacity={0.9}
onPressIn={() => (scale.value = withSpring(0.96))}
onPressOut={() => (scale.value = withSpring(1))}
onPress={() =>
handleMarkAsRead(
item.messageId,
item?.data?.msgType,
item?.data?.groupId,
item?.data?.cycleNumber,
item?.data?.contributionId
)
}
>
<LinearGradient
colors={
item.read
? [theme.colors.white, '#F8FAFF']
: ['#FFF7ED', '#EEF2FF']
}
style={[
styles(theme).card,
!item.read && styles(theme).unread,
{
borderLeftColor: getNotificationColor(
item.data.msgType
),
},
]}
>
<View style={styles(theme).row}>
{/* ICON */}
<View style={styles(theme).iconBox}>
<LucideIcon
name={getNotificationIcon(
item.data.msgType
)}
size={22}
color={getNotificationColor(
item.data.msgType
)}
/>
</View>
{/* TEXT */}
<View style={styles(theme).content}>
<View style={styles(theme).topRow}>
<Text
style={[
styles(theme).title,
!item.read &&
styles(theme).unreadTitle,
]}
>
{item.data.title}
</Text>
<Text style={styles(theme).time}>
{formatTime(item.sentTime)}
</Text>
</View>
<Text style={styles(theme).message}>
{item.data.body}
</Text>
<View style={styles(theme).tagWrap}>
<Text
style={[
styles(theme).tag,
{
backgroundColor:
getNotificationColor(
item.data.msgType
),
},
]}
>
{getFilterDisplayName(
item.data.msgType
)}
</Text>
</View>
</View>
{/* DOT */}
{!item.read && (
<View style={styles(theme).dot} />
)}
</View>
</LinearGradient>
</TouchableOpacity>
</Animated.View>
</Swipeable>
);
};
return ( return (
<AppView> <AppView>
{/* Header with actions */}
<Header <Header
title={`Notifications ${unreadCount > 0 ? `(${unreadCount})` : ''}`} title={
'Notifications' +
(unreadCount ? ` (${unreadCount})` : '')
}
/> />
{/* <View style={styles.headerActions}>
{unreadCount > 0 && (
<LinearGradient
colors={[colors.primary, colors.primary]} // Pastel Green gradient
style={[styles.headerButton, styles.markReadButton]}>
<TouchableOpacity onPress={handleMarkAllAsRead}>
<Text style={styles.headerButtonText}>Mark All Read</Text>
</TouchableOpacity>
</LinearGradient>
)}
{notifications.length > 0 && (
<LinearGradient
colors={[colors.pastelPink, colors.primary]} // Pastel Pink to Pastel Orange gradient
style={styles.headerButton}>
<TouchableOpacity onPress={handleClearAll}>
<Text style={styles.headerButtonText}>Clear All</Text>
</TouchableOpacity>
</LinearGradient>
)}
</View> */}
{/* Notification List */}
<FlatList <FlatList
data={notifications} data={notifications}
keyExtractor={(item) => item.messageId} keyExtractor={(item) => item.messageId}
renderItem={renderItem}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
contentContainerStyle={styles.listContainer} contentContainerStyle={styles(theme).container}
renderItem={({ item }) => {
console.log("noti items : ", item)
return (
<TouchableOpacity
onPress={() => handleMarkAsRead(item.messageId, item?.data?.msgType, item?.data?.groupId, item?.data?.cycleNumber, item?.data?.contributionId)}
activeOpacity={0.7}
>
<LinearGradient
colors={item.read ? ['#F5F5F5', '#E8E8E8'] : ['#FFFFFF', '#F8F8FF']} // Pastel grays and whites
style={[
styles.notificationCard,
!item.read && styles.unreadNotification,
{ borderLeftColor: getNotificationColor(item.data.msgType) }
]}>
<View style={styles.notificationContent}>
{/* Icon */}
<View style={styles.iconContainer}>
<LucideIcon
name={getNotificationIcon(item.data.msgType)}
size={24}
color={getNotificationColor(item.data.msgType)}
/>
</View>
{/* Content */}
<View style={styles.textContent}>
<View style={styles.headerRow}>
<Text style={[
styles.title,
!item.read && styles.unreadTitle
]}>
{item.data.title}
</Text>
<Text style={styles.time}>
{formatTime(item.sentTime)}
</Text>
</View>
<Text style={styles.message}>
{item.data.body}
</Text>
{item.data.msgType && (
<View style={styles.tagContainer}>
<Text style={[
styles.tag,
{ backgroundColor: getNotificationColor(item.data.msgType) }
]}>
{getFilterDisplayName(item.data.msgType as FilterType)}
</Text>
</View>
)}
</View>
{/* Actions */}
<View style={styles.actionsContainer}>
{!item.read && (
<View style={styles.unreadIndicator} />
)}
<TouchableOpacity
onPress={() => handleDelete(item.messageId)}
style={styles.deleteButton}
>
<LucideIcon name="trash-outline" size={20} color={colors.pastelPink} /> {/* Pastel Pink */}
</TouchableOpacity>
</View>
</View>
</LinearGradient>
</TouchableOpacity>
)
}}
ListEmptyComponent={ ListEmptyComponent={
<View style={styles.emptyContainer}> <View style={styles(theme).empty}>
<LucideIcon name="notifications-off-outline" size={64} color="#D3D3D3" /> {/* Pastel Gray */} <LucideIcon
<Text style={styles.emptyText}>No notifications yet</Text> name="notifications-off-outline"
<Text style={styles.emptySubtext}> size={64}
When you get notifications, they'll appear here color="#ccc"
/>
<Text style={styles(theme).emptyText}>
No notifications
</Text> </Text>
</View> </View>
} }
@@ -284,130 +275,119 @@ const Notification = () => {
export default Notification; export default Notification;
const styles = StyleSheet.create({ const styles = (theme: any) =>
headerActions: { StyleSheet.create({
flexDirection: 'row', container: {
gap: 8, padding: 12,
justifyContent: 'flex-end', paddingBottom: 40,
paddingHorizontal: 16, },
paddingVertical: 10,
}, card: {
headerButton: { borderRadius: 16,
borderRadius: 8, padding: 16,
paddingVertical: 6, marginBottom: 12,
paddingHorizontal: 12, backgroundColor: 'rgba(255,255,255,0.95)',
minWidth: 80, shadowColor: '#000',
alignItems: 'center', shadowOpacity: 0.08,
}, shadowRadius: 10,
markReadButton: { elevation: 4,
minWidth: 100, borderLeftWidth: 5,
}, },
headerButtonText: {
color: '#FFFFFF', // White text for better contrast on pastel backgrounds unread: {
fontWeight: '600', borderWidth: 1,
fontSize: 12, borderColor: '#E0E7FF',
}, shadowColor: '#6366F1',
listContainer: { shadowOpacity: 0.15,
paddingHorizontal: 12, },
paddingBottom: 30,
paddingTop: 8, row: {
}, flexDirection: 'row',
notificationCard: { gap: 12,
borderRadius: 12, },
padding: 16,
marginBottom: 10, iconBox: {
shadowColor: '#000', backgroundColor: '#F1F5F9',
shadowOpacity: 0.05, // Reduced opacity for softer shadow padding: 10,
shadowRadius: 4, borderRadius: 12,
elevation: 2, },
borderLeftWidth: 4,
borderLeftColor: 'transparent', content: {
}, flex: 1,
unreadNotification: { },
backgroundColor: '#F8F8FF', // Pastel Lavender
}, topRow: {
notificationContent: { flexDirection: 'row',
flexDirection: 'row', justifyContent: 'space-between',
alignItems: 'flex-start', },
gap: 12,
}, title: {
iconContainer: { fontWeight: '700',
paddingTop: 2, fontSize: 15,
}, color: theme.colors.textPrimary,
textContent: { flex: 1,
flex: 1, },
gap: 4,
}, unreadTitle: {
headerRow: { color: theme.colors.primary,
flexDirection: 'row', },
justifyContent: 'space-between',
alignItems: 'flex-start', message: {
marginBottom: 4, fontSize: 13,
}, color: theme.colors.textSecondary,
title: { marginTop: 4,
fontWeight: '600', },
fontSize: 16,
color: '#696969', // Dim Gray time: {
flex: 1, fontSize: 11,
marginRight: 8, color: '#6B7280',
}, },
unreadTitle: {
fontWeight: '700', tagWrap: {
color: '#2F4F4F', // Dark Slate Gray marginTop: 6,
}, },
message: {
fontSize: 14, tag: {
color: '#808080', // Gray fontSize: 10,
lineHeight: 20, color: '#fff',
}, paddingHorizontal: 10,
time: { paddingVertical: 4,
fontSize: 12, borderRadius: 20,
color: '#A9A9A9', // Dark Gray fontWeight: '700',
fontWeight: '500', alignSelf: 'flex-start',
}, },
tagContainer: {
marginTop: 6, dot: {
}, width: 8,
tag: { height: 8,
fontSize: 10, borderRadius: 4,
color: '#FFFFFF', // White text for better contrast backgroundColor: theme.colors.primary,
paddingHorizontal: 8, marginTop: 6,
paddingVertical: 2, },
borderRadius: 4,
overflow: 'hidden', deleteSwipe: {
alignSelf: 'flex-start', backgroundColor: '#FF3B30',
fontWeight: '600', justifyContent: 'center',
}, alignItems: 'center',
actionsContainer: { width: 80,
alignItems: 'center', borderRadius: 16,
gap: 8, marginBottom: 12,
}, },
unreadIndicator: {
width: 8, deleteText: {
height: 8, color: '#fff',
borderRadius: 4, fontSize: 12,
backgroundColor: '#87CEEB', // Pastel Sky Blue marginTop: 4,
}, },
deleteButton: {
padding: 4, empty: {
}, alignItems: 'center',
emptyContainer: { marginTop: 120,
alignItems: 'center', },
justifyContent: 'center',
marginTop: 100, emptyText: {
paddingHorizontal: 40, marginTop: 12,
}, color: '#999',
emptyText: { fontSize: 16,
fontSize: 18, },
fontWeight: '600', });
color: '#A9A9A9', // Dark Gray
marginTop: 16,
marginBottom: 8,
},
emptySubtext: {
fontSize: 14,
color: '#C0C0C0', // Silver
textAlign: 'center',
lineHeight: 20,
},
});
+7 -5
View File
@@ -319,7 +319,9 @@ const PaymentModeScreen: React.FC = () => {
`${BASE_URL}payments/pg/entry-fee/${groupId}?groupId=${groupId}&paymentId=${selectedMode}&mobileNumber=`, `${BASE_URL}payments/pg/entry-fee/${groupId}?groupId=${groupId}&paymentId=${selectedMode}&mobileNumber=`,
'POST', 'POST',
payload payload
) as any; ) as any;
console.log("payment res",res)
if (res?.status === 'success' || res?.statusCode === 200) { if (res?.status === 'success' || res?.statusCode === 200) {
const pgOrderNo = res?.data?.pgOrderNo; const pgOrderNo = res?.data?.pgOrderNo;
@@ -356,7 +358,8 @@ const PaymentModeScreen: React.FC = () => {
'POST', 'POST',
payload payload
) as any; ) as any;
console.log("payment payload ",payload)
if (res?.status === 'success' || res?.statusCode === 200) { if (res?.status === 'success' || res?.statusCode === 200) {
const pgOrderNo = res?.data?.pgOrderNo; const pgOrderNo = res?.data?.pgOrderNo;
if (pgOrderNo) { if (pgOrderNo) {
@@ -467,8 +470,8 @@ const PaymentModeScreen: React.FC = () => {
<ActivityIndicator color="#fff" size="small" /> <ActivityIndicator color="#fff" size="small" />
) : ( ) : (
<> <>
<LucideIcon name="shield-checkmark-outline" size={20} color="#fff" style={{ marginRight: 8 }} /> <LucideIcon name="shield-checkmark-outline" size={20} color={theme.colors.reverseText} style={{ marginRight: 8 }} />
<Text style={styles.confirmText}>Confirm & Pay</Text> <Text style={[styles.confirmText, { color: theme.colors.black }]}>Confirm & Pay</Text>
</> </>
)} )}
</TouchableOpacity> </TouchableOpacity>
@@ -685,7 +688,6 @@ const styles = StyleSheet.create({
borderRadius: 14, borderRadius: 14,
}, },
confirmText: { confirmText: {
color: '#fff',
fontSize: responsiveFontSize(1.8), fontSize: responsiveFontSize(1.8),
fontFamily: FontsFamily.nunitoBold, fontFamily: FontsFamily.nunitoBold,
}, },
+45 -1
View File
@@ -9,6 +9,8 @@ import {
Alert, Alert,
RefreshControl, RefreshControl,
ToastAndroid, ToastAndroid,
Modal,
ActivityIndicator,
} from "react-native"; } from "react-native";
import * as Keychain from "react-native-keychain"; import * as Keychain from "react-native-keychain";
import LucideIcon from '../../components/common/LucideIcon'; import LucideIcon from '../../components/common/LucideIcon';
@@ -118,6 +120,7 @@ const Profile = () => {
countryCode: "", countryCode: "",
}); });
const [mobileMoneyErrors, setMobileMoneyErrors] = useState<MobileMoneyErrors>({}); const [mobileMoneyErrors, setMobileMoneyErrors] = useState<MobileMoneyErrors>({});
const [isLoggingOut, setIsLoggingOut] = useState(false);
// --- API HOOKS --- // --- API HOOKS ---
const { const {
@@ -181,6 +184,7 @@ const Profile = () => {
}, [refetchProfile, refetchStatus]); }, [refetchProfile, refetchStatus]);
const handleLogout = async () => { const handleLogout = async () => {
setIsLoggingOut(true);
try { try {
const fcmToken = await getFcmToken(user?.userId?.toString()); const fcmToken = await getFcmToken(user?.userId?.toString());
if (fcmToken) { if (fcmToken) {
@@ -199,6 +203,7 @@ const Profile = () => {
navigation.replace("Login"); navigation.replace("Login");
} catch (error) { } catch (error) {
console.error("Logout error:", error); console.error("Logout error:", error);
setIsLoggingOut(false);
} }
}; };
@@ -671,6 +676,19 @@ const Profile = () => {
</> </>
)} )}
</ScrollView> </ScrollView>
<Modal
visible={isLoggingOut}
transparent={true}
animationType="fade"
>
<View style={styles.loadingOverlay}>
<View style={[styles.loadingBox, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}>
<ActivityIndicator size="large" color={theme.colors.primary} />
<Text style={[styles.loadingText, { color: theme.colors.textPrimary }]}>Signing you out...</Text>
</View>
</View>
</Modal>
</AppView> </AppView>
); );
}; };
@@ -883,7 +901,33 @@ const styles = StyleSheet.create({
skeletonContainer: { skeletonContainer: {
paddingHorizontal: 14, paddingHorizontal: 14,
marginTop: 14, marginTop: 14,
} },
loadingOverlay: {
flex: 1,
backgroundColor: 'rgba(0, 0, 0, 0.4)',
justifyContent: 'center',
alignItems: 'center',
padding: 20,
},
loadingBox: {
padding: 24,
borderRadius: 20,
alignItems: 'center',
width: '70%',
maxWidth: 280,
borderWidth: 1,
elevation: 5,
shadowColor: '#000',
shadowOpacity: 0.1,
shadowRadius: 10,
shadowOffset: { width: 0, height: 4 },
},
loadingText: {
marginTop: 16,
fontSize: responsiveFontSize(1.8),
fontFamily: FontsFamily.nunitoBold,
textAlign: 'center',
},
}); });
export default Profile; export default Profile;
+3 -2
View File
@@ -26,7 +26,8 @@ const SwitchNavigator: React.FC<SwitchNavigatorProps> = ({ role, status }) => {
return ( return (
<TouchableOpacity <TouchableOpacity
activeOpacity={0.88} activeOpacity={0.88}
onPress={() => navigation.navigate("BecomeOrg")} onPress={() => navigation.navigate("BecomeOrg")}
disabled={prettyStatus === "PENDING"}
style={[styles.container, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]} style={[styles.container, { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }]}
> >
<View style={[styles.leadingIcon, { backgroundColor: theme.colors.primarySoft }]}> <View style={[styles.leadingIcon, { backgroundColor: theme.colors.primarySoft }]}>
@@ -42,7 +43,7 @@ const SwitchNavigator: React.FC<SwitchNavigatorProps> = ({ role, status }) => {
<Text style={[styles.secondaryText, { color: theme.colors.textSecondary }]}>{secondaryText}</Text> <Text style={[styles.secondaryText, { color: theme.colors.textSecondary }]}>{secondaryText}</Text>
</View> </View>
{ prettyStatus !== "PENDING" && <View style={[styles.arrowBox, { borderColor: theme.colors.border, backgroundColor: theme.colors.background }]}> {prettyStatus !== "PENDING" && <View style={[styles.arrowBox, { borderColor: theme.colors.border, backgroundColor: theme.colors.background }]}>
<LucideIcon name="chevron-forward" size={18} color={theme.colors.textSecondary} /> <LucideIcon name="chevron-forward" size={18} color={theme.colors.textSecondary} />
</View>} </View>}
</TouchableOpacity> </TouchableOpacity>
+1 -1
View File
@@ -52,7 +52,7 @@ const formatINR = (amount: number) => {
try { try {
return new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(amount); return new Intl.NumberFormat("en-IN", { style: "currency", currency: "INR" }).format(amount);
} catch (e) { } catch (e) {
return `₹${amount.toFixed(2)}`; return `UGX${amount.toFixed(2)}`;
} }
}; };
+1 -1
View File
@@ -1,7 +1,7 @@
export const BASE_URL = "https://11.godemo.in/FundCollect/api/" export const BASE_URL = "https://11.godemo.in/FundCollect/api/"
// export const BASE_URL = "http://192.168.29.113:9060/api/" // export const BASE_URL = "http://192.168.29.113:9060/api/"
export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobiles=` export const LOGIN_URL = `${BASE_URL}user/generate/otp?mobile=`
export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify` export const VARIFY_OTP_URL = `${BASE_URL}user/otp/verify`
export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply` export const ORGRAGISTRATION = `${BASE_URL}organizer/registration/apply`
export const GROUP_CREATION_URL = `${BASE_URL}fund/groups/{organizerId}` export const GROUP_CREATION_URL = `${BASE_URL}fund/groups/{organizerId}`