feat(app): improvement

This commit is contained in:
uttam05111990 2026-07-30 19:50:04 +05:30
parent e96ea0a490
commit 1f1b9aa264
37 changed files with 1319 additions and 564 deletions

View File

@ -2,6 +2,9 @@
<style name="AppTheme" parent="Theme.AppCompat.DayNight.NoActionBar">
<!-- Customize your theme here. -->
<item name="android:editTextBackground">@drawable/rn_edit_text_material</item>
<!-- Opt out of Android 15+ forced edge-to-edge so the status bar
behaves predictably with React Native's StatusBar component. -->
<item name="android:windowOptOutEdgeToEdgeEnforcement">true</item>
</style>
<style name="BootTheme" parent="Theme.BootSplash">

View File

@ -1,5 +1,5 @@
import React, { useEffect } from 'react';
import { StatusBar } from 'react-native';
import React, { useEffect, useRef } from 'react';
import { StatusBar, AppState, AppStateStatus, Platform } from 'react-native';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import { RootNavigator } from './navigation/rootNavigator';
import { ThemeProvider, useTheme } from './theme';
@ -8,6 +8,8 @@ import { store, persistor, useAppDispatch, useAppSelector, getStatusList, getSou
import { PersistGate } from 'redux-persist/integration/react';
import { NotificationService } from '@services';
import BootSplash from 'react-native-bootsplash';
import messaging from '@react-native-firebase/messaging';
import { getStaffNotifications } from './features/notification/thunk';
const ThemedStatusBar = () => {
const { theme: colors, isDark } = useTheme();
@ -15,12 +17,16 @@ const ThemedStatusBar = () => {
useEffect(() => {
StatusBar.setBarStyle(isDark ? 'light-content' : 'dark-content', true);
StatusBar.setBackgroundColor(colors.background, true);
if (Platform.OS === 'android') {
StatusBar.setTranslucent(false);
}
}, [colors, isDark]);
return (
<StatusBar
barStyle={isDark ? 'light-content' : 'dark-content'}
backgroundColor={colors.background}
translucent={false}
/>
);
};
@ -40,7 +46,33 @@ const AppInit = () => {
}
}, [dispatch, token]);
// When a push notification arrives (foreground), re-fetch from the server
// so the list and unread badge are always accurate
useEffect(() => {
const unsubscribe = messaging().onMessage(async () => {
const staffId = (store.getState() as any).auth?.user_data?.staffid;
if (staffId) {
dispatch(getStaffNotifications(staffId));
}
});
return unsubscribe;
}, [dispatch]);
// When the app comes back to the foreground from background (e.g., user taps
// a background push notification), re-fetch so count and list are up to date
const appState = useRef<AppStateStatus>(AppState.currentState);
useEffect(() => {
const subscription = AppState.addEventListener('change', nextState => {
if (appState.current.match(/inactive|background/) && nextState === 'active') {
const staffId = (store.getState() as any).auth?.user_data?.staffid;
if (staffId) {
dispatch(getStaffNotifications(staffId));
}
}
appState.current = nextState;
});
return () => subscription.remove();
}, [dispatch]);
return null;
};

View File

@ -1,4 +1,4 @@
import { LoginRequest, LoginResponse } from '@interfaces';
import { LoginRequest, LoginResponse, LogoutRequest, LogoutResponse } from '@interfaces';
import { api } from '@utils';
export const loginApi = async (
@ -7,8 +7,20 @@ export const loginApi = async (
const formData = new FormData();
formData.append('email', payload.email);
formData.append('password', payload.password);
if(payload.device_token) {
formData.append('device_token', String(payload.device_token));
if(payload.fcm_token) {
formData.append('fcm_token', String(payload.fcm_token));
}
if(payload.device_id) {
formData.append('device_id', String(payload.device_id));
}
return await api.post<LoginResponse>('/api/stafflogin', formData);
};
export const logoutApi = async (
payload: LogoutRequest,
): Promise<LogoutResponse> => {
const formData = new FormData();
formData.append('id', payload.id);
formData.append('fcm_token', payload.fcm_token);
return await api.post<LogoutResponse>('api/stafflogin/logout', formData);
};

View File

@ -0,0 +1,10 @@
import { DashboardLeadCountItem, DashboardLeadCountPayload } from '@interfaces';
import { api } from '@utils';
export const getDashboardLeadCountApi = async (
payload: DashboardLeadCountPayload,
): Promise<DashboardLeadCountItem[]> => {
return await api.get<DashboardLeadCountItem[]>(
`/api/lead_count_dashboard/${payload.action}/${payload.staffid}`,
);
};

View File

@ -4,5 +4,8 @@ export * from './leadDetailsApi';
export * from './listApi';
export * from './customersApi';
export * from './fcmTokenApi';
export * from './notificationApi';
export * from './dashboardLeadCountApi';

View File

@ -0,0 +1,24 @@
import { NotificationItem, MarkNotificationReadResponse, ClearNotificationsResponse } from '@interfaces';
import { api } from '@utils';
export const getStaffNotificationsApi = async (
staffId: string,
): Promise<NotificationItem[]> => {
const url = `/api/staff_notification/${staffId}`;
return await api.get<NotificationItem[]>(url);
};
export const markNotificationReadApi = async (
staffId: string,
notificationId: string,
): Promise<MarkNotificationReadResponse> => {
const url = `/api/staff_notification_read/${staffId}/${notificationId}`;
return await api.post<MarkNotificationReadResponse>(url, {});
};
export const clearNotificationsApi = async (
staffId: string,
): Promise<ClearNotificationsResponse> => {
const url = `/api/staff_notification_clear/${staffId}`;
return await api.post<ClearNotificationsResponse>(url, {});
};

View File

@ -12,6 +12,8 @@ export * from './statCard';
export * from './profileInfoRow';
export * from './customerItemCard';
export * from './customerDetailHeader';
export * from './notificationItem';
export * from './leadDistribution';

View File

@ -0,0 +1,3 @@
export * from './leadDistribution';
export * from './leadDistribution.props';

View File

@ -0,0 +1,9 @@
import { DashboardLeadCountItem } from '@interfaces';
export interface LeadDistributionProps {
data: DashboardLeadCountItem[];
loading?: boolean;
activeAction: string;
onActionChange: (action: string) => void;
}

View File

@ -0,0 +1,224 @@
import { StyleSheet } from 'react-native';
import { ThemeColors, colors as globalColors } from '../../theme';
export const getStyles = (colors: ThemeColors, isDark: boolean) =>
StyleSheet.create({
card: {
backgroundColor: colors.card,
borderRadius: 20,
marginBottom: 20,
shadowColor: isDark ? '#000000' : '#0F172A',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: isDark ? 0.25 : 0.08,
shadowRadius: 12,
elevation: 4,
borderWidth: 1,
borderColor: colors.border,
},
/* ── Header ── */
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
paddingTop: 16,
paddingBottom: 14,
},
titleRow: {
flexDirection: 'row',
alignItems: 'center',
gap: 10,
},
titleIconBadge: {
width: 34,
height: 34,
borderRadius: 10,
backgroundColor: globalColors.primary,
justifyContent: 'center',
alignItems: 'center',
shadowColor: globalColors.primary,
shadowOffset: { width: 0, height: 3 },
shadowOpacity: 0.4,
shadowRadius: 6,
elevation: 4,
},
title: {
fontSize: 15,
fontWeight: '700',
color: colors.text,
letterSpacing: 0.1,
},
subtitle: {
fontSize: 11,
fontWeight: '500',
color: colors.textMuted,
marginTop: 1,
letterSpacing: 0.2,
textTransform: 'lowercase',
},
/* ── Dropdown Pill ── */
dropdown: {
flexDirection: 'row',
alignItems: 'center',
gap: 5,
paddingHorizontal: 11,
paddingVertical: 7,
borderRadius: 20,
borderWidth: 1.5,
borderColor: isDark
? `${globalColors.primary}55`
: `${globalColors.primary}33`,
backgroundColor: isDark
? `${globalColors.primary}18`
: `${globalColors.primary}0D`,
},
dropdownDot: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: globalColors.primary,
},
dropdownText: {
fontSize: 12,
fontWeight: '700',
color: globalColors.primary,
letterSpacing: 0.2,
},
/* ── Divider ── */
divider: {
height: 1,
backgroundColor: colors.border,
marginHorizontal: 16,
marginBottom: 16,
opacity: isDark ? 0.5 : 0.8,
},
/* ── Body ── */
body: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingBottom: 16,
},
chartContainer: {
width: 140,
height: 140,
justifyContent: 'center',
alignItems: 'center',
position: 'relative',
},
loaderContainer: {
flex: 1,
height: 140,
justifyContent: 'center',
alignItems: 'center',
},
centerLabelContainer: {
justifyContent: 'center',
alignItems: 'center',
},
centerNumber: {
fontSize: 24,
fontWeight: '800',
color: colors.text,
lineHeight: 28,
},
centerText: {
fontSize: 12,
color: colors.textMuted,
fontWeight: '600',
marginTop: 2,
},
/* ── Legend ── */
legendContainer: {
flex: 1,
marginLeft: 16,
maxHeight: 140,
},
legendItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 7,
},
legendItemLeft: {
flexDirection: 'row',
alignItems: 'center',
flex: 1,
},
bullet: {
width: 10,
height: 10,
borderRadius: 5,
marginRight: 8,
},
legendLabel: {
fontSize: 13,
fontWeight: '600',
color: colors.text,
},
legendItemRight: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'flex-end',
width: 76,
},
legendCount: {
fontSize: 13,
fontWeight: '700',
color: colors.text,
textAlign: 'right',
width: 32,
},
legendPercentage: {
fontSize: 12,
color: colors.textMuted,
textAlign: 'right',
width: 44,
},
/* ── Dropdown Menu ── */
menuOverlay: {
position: 'absolute',
top: 52,
right: 14,
backgroundColor: colors.card,
borderRadius: 12,
borderWidth: 1,
borderColor: colors.border,
zIndex: 9999,
elevation: 20,
shadowColor: '#000',
shadowOffset: { width: 0, height: 6 },
shadowOpacity: isDark ? 0.35 : 0.12,
shadowRadius: 12,
minWidth: 120,
},
menuItem: {
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'space-between',
paddingVertical: 11,
paddingHorizontal: 16,
},
menuItemActive: {
backgroundColor: isDark
? `${globalColors.primary}25`
: `${globalColors.primary}0F`,
},
menuItemText: {
fontSize: 13,
fontWeight: '600',
color: colors.textSecondary,
},
menuItemTextActive: {
color: globalColors.primary,
fontWeight: '700',
},
});

View File

@ -0,0 +1,166 @@
import React, { useState } from 'react';
import { View, Text, TouchableOpacity, ActivityIndicator, ScrollView } from 'react-native';
import { PieChart } from 'react-native-gifted-charts';
import Icon from 'react-native-vector-icons/Ionicons';
import { useTheme } from '@theme';
import { getStyles } from './leadDistribution.styles';
import { LeadDistributionProps } from './leadDistribution.props';
export const LeadDistribution = ({
data = [],
loading = false,
activeAction,
onActionChange,
}: LeadDistributionProps) => {
const { theme: colors, isDark } = useTheme();
const styles = getStyles(colors, isDark);
const [menuVisible, setMenuVisible] = useState(false);
const actions = [
{ label: 'Day', value: 'day' },
{ label: 'Week', value: 'week' },
{ label: 'Month', value: 'month' },
{ label: 'Quarter', value: 'quarter' },
{ label: 'Year', value: 'year' },
];
const currentActionLabel =
actions.find((a) => a.value === activeAction)?.label || 'Month';
const totalLeadItem = data.find((item) => item.name === 'Total Lead');
const totalCount = totalLeadItem
? totalLeadItem.count
: data.reduce((sum, item) => sum + item.count, 0);
const displayData = data
.filter((item) => item.name !== 'Total Lead' && item.count > 0)
.map((item) => ({
...item,
percentage: totalCount > 0 ? Math.round((item.count / totalCount) * 100) : 0,
}));
const pieData = displayData.map((item) => ({
value: item.count,
color: item.color,
}));
const renderCenterLabel = () => {
return (
<View style={styles.centerLabelContainer}>
<Text style={styles.centerNumber}>{totalCount}</Text>
<Text style={styles.centerText}>Total</Text>
</View>
);
};
return (
<View style={styles.card}>
{/* Header */}
<View style={styles.header}>
<View style={styles.titleRow}>
<View style={styles.titleIconBadge}>
<Icon name="pie-chart" size={15} color="#FFFFFF" />
</View>
<View>
<Text style={styles.title}>Lead Distribution</Text>
<Text style={styles.subtitle}>by status · {currentActionLabel}</Text>
</View>
</View>
<TouchableOpacity
style={styles.dropdown}
onPress={() => setMenuVisible(!menuVisible)}
activeOpacity={0.7}
>
<View style={styles.dropdownDot} />
<Text style={styles.dropdownText}>{currentActionLabel}</Text>
<Icon
name={menuVisible ? 'chevron-up' : 'chevron-down'}
size={13}
color={colors.icon}
/>
</TouchableOpacity>
</View>
{/* Dropdown Menu */}
{menuVisible && (
<View style={styles.menuOverlay}>
{actions.map((act) => {
const isSelected = act.value === activeAction;
return (
<TouchableOpacity
key={act.value}
style={[styles.menuItem, isSelected && styles.menuItemActive]}
onPress={() => {
onActionChange(act.value);
setMenuVisible(false);
}}
>
<Text style={[styles.menuItemText, isSelected && styles.menuItemTextActive]}>
{act.label}
</Text>
{isSelected && (
<Icon name="checkmark" size={14} color={colors.icon} />
)}
</TouchableOpacity>
);
})}
</View>
)}
{/* Divider */}
<View style={styles.divider} />
{/* Body — fixed min-height so card never jumps */}
<View style={styles.body}>
{loading ? (
<View style={styles.loaderContainer}>
<ActivityIndicator size="large" color={colors.icon} />
</View>
) : displayData.length === 0 ? (
<View style={styles.loaderContainer}>
<Text style={{ color: colors.textSecondary, fontSize: 13 }}>
No lead data available
</Text>
</View>
) : (
<>
<View style={styles.chartContainer}>
<PieChart
data={pieData}
donut
radius={60}
innerRadius={45}
innerCircleColor={colors.card}
centerLabelComponent={renderCenterLabel}
/>
</View>
<ScrollView
style={styles.legendContainer}
showsVerticalScrollIndicator={false}
nestedScrollEnabled
>
{displayData.map((item, idx) => (
<View key={idx} style={styles.legendItem}>
<View style={styles.legendItemLeft}>
<View style={[styles.bullet, { backgroundColor: item.color }]} />
<Text style={styles.legendLabel} numberOfLines={1}>
{item.name}
</Text>
</View>
<View style={styles.legendItemRight}>
<Text style={styles.legendCount}>{item.count}</Text>
<Text style={styles.legendPercentage}>{item.percentage}%</Text>
</View>
</View>
))}
</ScrollView>
</>
)}
</View>
</View>
);
};

View File

@ -0,0 +1 @@
export * from './notificationItem';

View File

@ -0,0 +1,63 @@
import { StyleSheet } from 'react-native';
import { ThemeColors } from '@theme';
export const getStyles = (colors: ThemeColors) =>
StyleSheet.create({
card: {
backgroundColor: colors.surface,
paddingVertical: 14,
paddingHorizontal: 16,
borderBottomWidth: 1,
borderBottomColor: colors.border,
flexDirection: 'row',
alignItems: 'flex-start',
},
unreadCard: {
backgroundColor: `${colors.icon}06`,
},
iconWrapper: {
width: 36,
height: 36,
borderRadius: 18,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
marginTop: 2,
},
profileImage: {
width: 36,
height: 36,
borderRadius: 18,
},
contentContainer: {
flex: 1,
},
titleRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 3,
},
title: {
fontSize: 14,
fontWeight: '600',
color: colors.text,
flex: 1,
},
unreadDot: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: '#EF4444',
marginLeft: 6,
},
description: {
fontSize: 13,
color: colors.textSecondary,
lineHeight: 18,
marginBottom: 6,
},
time: {
fontSize: 11,
color: colors.textMuted,
},
});

View File

@ -0,0 +1,56 @@
import React from 'react';
import { View, Text, TouchableOpacity, Image } from 'react-native';
import Icon from 'react-native-vector-icons/Ionicons';
import { useTheme } from '@theme';
import { getStyles } from './notificationItem.styles';
import { NotificationItem as NotificationItemType } from '@interfaces';
import dayjs from 'dayjs';
import relativeTime from 'dayjs/plugin/relativeTime';
dayjs.extend(relativeTime);
interface NotificationItemProps {
item: NotificationItemType;
onPress?: (item: NotificationItemType) => void;
}
export const NotificationItem: React.FC<NotificationItemProps> = ({ item, onPress }) => {
const { theme: colors } = useTheme();
const styles = getStyles(colors);
const isUnread = String(item.isread) === '0';
const cleanImageUrl = item.profile_image ? item.profile_image.replace(/"/g, '').trim() : '';
return (
<TouchableOpacity
style={[styles.card, isUnread && styles.unreadCard]}
activeOpacity={0.7}
onPress={() => onPress && onPress(item)}
>
<View style={styles.iconWrapper}>
{cleanImageUrl ? (
<Image
source={{ uri: cleanImageUrl }}
style={styles.profileImage}
resizeMode="cover"
/>
) : (
<Icon name="person-circle-outline" size={38} color={colors.icon ?? '#888'} />
)}
</View>
<View style={styles.contentContainer}>
<View style={styles.titleRow}>
<Text style={styles.title} numberOfLines={1}>
{item.from_fullname || 'Notification'}
</Text>
<Text style={styles.time}>{dayjs(item.full_date || item.date).fromNow()}</Text>
{isUnread && <View style={styles.unreadDot} />}
</View>
<Text style={styles.description} numberOfLines={2}>
{item.description}
</Text>
</View>
</TouchableOpacity>
);
};

View File

@ -1,124 +1,58 @@
/* eslint-disable react-native/no-inline-styles */
import React from 'react';
import { Text, View, ScrollView, TouchableOpacity } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { useNavigation } from '@react-navigation/native';
import Icon from 'react-native-vector-icons/Ionicons';
import React, { useEffect, useState } from 'react';
import { ScrollView, View, Text, InteractionManager } from 'react-native';
import { getStyles } from './dashboard.styles';
import { statCards } from '../../mock-data/dashboard';
import { useTheme } from '../../theme';
import { LeadDistribution } from '@components';
import { useAppDispatch, useAppSelector, RootState } from '@store';
import { getDashboardLeadCount } from './thunk';
import { NotificationService } from '@services';
export const DashboardScreen = () => {
const navigation = useNavigation<any>();
const dispatch = useAppDispatch();
const { theme: colors } = useTheme();
const styles = getStyles(colors);
const handleLogout = async () => {
try {
await AsyncStorage.removeItem('userToken');
await AsyncStorage.removeItem('tenancyName');
} catch (e) {
console.error(e);
}
navigation.reset({
index: 0,
routes: [{ name: 'AuthStack' }], // root-level stack name stays as-is
});
};
const [selectedAction, setSelectedAction] = useState<string>('year');
const user_data = useAppSelector((state: RootState) => state.auth.user_data);
const { leadCountData, loading, error } = useAppSelector(
(state: RootState) => state.dashboard,
);
useEffect(() => {
const task = InteractionManager.runAfterInteractions(() => {
NotificationService.requestPermission();
});
return () => task.cancel();
}, []);
useEffect(() => {
if (user_data?.staffid) {
dispatch(
getDashboardLeadCount({
action: selectedAction,
staffid: user_data.staffid,
}),
);
}
}, [dispatch, selectedAction, user_data]);
return (
<ScrollView style={styles.container} contentContainerStyle={styles.content}>
{/* Hero Welcome banner */}
<View style={styles.heroCard}>
<View>
<Text style={styles.heroGreeting}>Welcome Back,</Text>
<Text style={styles.heroName}>Workspace Admin</Text>
<Text style={styles.heroInfo}>Convex CRM Active session</Text>
{error && (
<View style={styles.errorContainer}>
<Text style={styles.errorText}>{error}</Text>
</View>
<TouchableOpacity style={styles.logoutButton} onPress={handleLogout}>
<Icon name="log-out-outline" size={22} color="#EF4444" />
</TouchableOpacity>
</View>
)}
{/* Grid of Stats */}
<Text style={styles.sectionTitle}>Overview Metrics</Text>
<View style={styles.statsGrid}>
{statCards.map((stat, idx) => (
<View key={idx} style={styles.statCard}>
<View style={styles.statHeader}>
<View
style={[
styles.statIconContainer,
{ backgroundColor: `${stat.color}15` },
]}
>
<Icon name={stat.icon} size={20} color={stat.color} />
</View>
<Text
style={[
styles.statChange,
{
color: stat.change.startsWith('+') ? '#10B981' : '#EF4444',
},
]}
>
{stat.change}
</Text>
</View>
<Text style={styles.statCount}>{stat.count}</Text>
<Text style={styles.statTitle}>{stat.title}</Text>
</View>
))}
</View>
{/* Recent Activity Section */}
<View style={styles.sectionHeader}>
<Text style={styles.sectionTitle}>Recent Activity</Text>
<TouchableOpacity>
<Text style={styles.seeAllLink}>See All</Text>
</TouchableOpacity>
</View>
<View style={styles.activityList}>
<View style={styles.activityItem}>
<View style={[styles.activityBadge, { backgroundColor: '#10B981' }]}>
<Icon name="person-add" size={14} color="#FFF" />
</View>
<View style={styles.activityDetails}>
<Text style={styles.activityTitle}>New Lead Created</Text>
<Text style={styles.activityDesc}>
Sarah Jenkins added by System Import
</Text>
<Text style={styles.activityTime}>2 minutes ago</Text>
</View>
</View>
<View style={styles.activityItem}>
<View style={[styles.activityBadge, { backgroundColor: '#3B82F6' }]}>
<Icon name="document-text" size={14} color="#FFF" />
</View>
<View style={styles.activityDetails}>
<Text style={styles.activityTitle}>Invoice #1042 Sent</Text>
<Text style={styles.activityDesc}>
Sent to Acme Corp for $1,250.00
</Text>
<Text style={styles.activityTime}>1 hour ago</Text>
</View>
</View>
<View style={styles.activityItem}>
<View style={[styles.activityBadge, { backgroundColor: '#F59E0B' }]}>
<Icon name="chatbubble-ellipses" size={14} color="#FFF" />
</View>
<View style={styles.activityDetails}>
<Text style={styles.activityTitle}>Ticket #2045 Replied</Text>
<Text style={styles.activityDesc}>
Support agent responded to email query
</Text>
<Text style={styles.activityTime}>3 hours ago</Text>
</View>
</View>
</View>
{/* Lead Distribution Chart Card */}
<LeadDistribution
data={leadCountData}
loading={loading}
activeAction={selectedAction}
onActionChange={setSelectedAction}
/>
</ScrollView>
);
};

View File

@ -9,149 +9,21 @@ export const getStyles = (colors: ThemeColors) => StyleSheet.create({
content: {
padding: 16,
},
heroCard: {
backgroundColor: colors.drawerHeader,
borderRadius: 16,
padding: 20,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 24,
shadowColor: '#000',
shadowOffset: { width: 0, height: 6 },
shadowOpacity: 0.1,
shadowRadius: 8,
elevation: 4,
},
heroGreeting: {
fontSize: 14,
color: '#94A3B8',
},
heroName: {
fontSize: 22,
fontWeight: '800',
color: '#FFFFFF',
marginTop: 2,
},
heroInfo: {
fontSize: 12,
color: colors.icon,
marginTop: 8,
fontWeight: '600',
},
logoutButton: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(255,255,255,0.1)',
justifyContent: 'center',
alignItems: 'center',
},
sectionHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
marginTop: 8,
},
sectionTitle: {
fontSize: 18,
fontWeight: '700',
color: colors.text,
marginBottom: 12,
},
seeAllLink: {
fontSize: 14,
color: colors.icon,
fontWeight: '600',
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginBottom: 20,
},
statCard: {
backgroundColor: colors.card,
borderRadius: 14,
padding: 16,
width: '48%',
errorContainer: {
padding: 12,
backgroundColor: 'rgba(239, 68, 68, 0.1)',
borderRadius: 8,
marginBottom: 16,
shadowColor: '#0F172A',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
borderWidth: 1,
borderColor: 'rgba(239, 68, 68, 0.3)',
},
statHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
statIconContainer: {
width: 36,
height: 36,
borderRadius: 10,
justifyContent: 'center',
alignItems: 'center',
},
statChange: {
fontSize: 12,
fontWeight: '700',
},
statCount: {
fontSize: 22,
fontWeight: '800',
color: colors.text,
},
statTitle: {
fontSize: 13,
color: colors.textSecondary,
marginTop: 4,
},
activityList: {
backgroundColor: colors.card,
borderRadius: 16,
padding: 16,
shadowColor: '#0F172A',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.05,
shadowRadius: 4,
elevation: 2,
},
activityItem: {
flexDirection: 'row',
marginBottom: 16,
},
activityBadge: {
width: 28,
height: 28,
borderRadius: 14,
justifyContent: 'center',
alignItems: 'center',
marginTop: 2,
},
activityDetails: {
marginLeft: 12,
flex: 1,
borderBottomWidth: 1,
borderBottomColor: colors.border,
paddingBottom: 12,
},
activityTitle: {
errorText: {
color: '#EF4444',
fontSize: 14,
fontWeight: '700',
color: colors.text,
},
activityDesc: {
fontSize: 13,
color: colors.textSecondary,
marginTop: 2,
},
activityTime: {
fontSize: 11,
color: colors.textMuted,
marginTop: 6,
textAlign: 'center',
fontWeight: '500',
},
});

View File

@ -0,0 +1,42 @@
import { createReducer } from '@reduxjs/toolkit';
import { DashboardLeadCountItem } from '@interfaces';
import { getDashboardLeadCount, resetDashboardState } from './thunk';
export interface DashboardState {
leadCountData: DashboardLeadCountItem[];
loading: boolean;
error: string | null;
}
const initialState: DashboardState = {
leadCountData: [],
loading: false,
error: null,
};
export const dashboardReducer = createReducer(initialState, builder => {
builder
.addCase(getDashboardLeadCount.pending, acc => {
acc.loading = true;
acc.error = null;
})
.addCase(getDashboardLeadCount.fulfilled, (acc, action) => {
acc.loading = false;
acc.leadCountData = action.payload;
acc.error = null;
})
.addCase(getDashboardLeadCount.rejected, (acc, action) => {
acc.loading = false;
acc.error =
(action.payload as string) ??
action.error.message ??
'Failed to load dashboard lead count';
})
.addCase(resetDashboardState, acc => {
acc.leadCountData = [];
acc.loading = false;
acc.error = null;
});
});
export default dashboardReducer;

View File

@ -0,0 +1,19 @@
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { getDashboardLeadCountApi } from '@api';
import { DashboardLeadCountItem, DashboardLeadCountPayload } from '@interfaces';
export const resetDashboardState = createAction('dashboard/resetState');
export const getDashboardLeadCount = createAsyncThunk<
DashboardLeadCountItem[],
DashboardLeadCountPayload
>(
'dashboard/getLeadCount',
async (payload, { rejectWithValue }) => {
try {
return await getDashboardLeadCountApi(payload);
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to load dashboard lead count');
}
},
);

View File

@ -9,19 +9,18 @@ import {
ImageBackground,
ActivityIndicator,
StatusBar,
Keyboard,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import Icon from 'react-native-vector-icons/Ionicons';
import { getStyles } from './login.styles';
import { useTheme } from '@theme';
import { useAppDispatch, useAppSelector, RootState, login, sendFcmToken } from '@store';
import { useAppDispatch, useAppSelector, RootState, login } from '@store';
import { LoginRequest } from '@interfaces';
import { NotificationService } from '@services';
import { backGroundImage } from '@utils';
import { FormInput } from '@components';
import config from 'react-native-config';
import AsyncStorage from '@react-native-async-storage/async-storage';
import DeviceInfo from 'react-native-device-info';
export const LoginScreen = () => {
const dispatch = useAppDispatch();
@ -36,7 +35,7 @@ export const LoginScreen = () => {
const [focusedField, setFocusedField] = useState<string | null>(null);
const scrollViewRef = useRef<ScrollView>(null);
const [localError, setLocalError] = useState('');
const [deviceToken, setDeviceToken] = useState<string | null>(null);
// const [deviceToken, setDeviceToken] = useState<string | null>(null);
const { loginLoading, loginError, loginSuccess, token, user_data } =
useAppSelector((state: RootState) => state.auth);
@ -62,30 +61,32 @@ export const LoginScreen = () => {
}
const fcmToken = await NotificationService.getFCMToken();
setDeviceToken(fcmToken);
console.log('deviceToken-', fcmToken)
const deviceId = await DeviceInfo.getUniqueId();
console.log('fcm_token-', fcmToken);
console.log('device_id-', deviceId);
const payload: LoginRequest = {
email,
password,
device_token: fcmToken ?? '',
fcm_token: fcmToken ?? '',
device_id: deviceId ?? '',
};
console.log('payload', payload)
// Build the full tenancy base URL and persist it in AsyncStorage
const cleanTenancy = tenancy.trim().replace(/^(https?:\/\/)?/, '');
const fullTenancy = `https://${cleanTenancy}`;
console.log('payload-', payload)
console.log('tenancy-', config.BASE_URL)
if (fullTenancy === config.BASE_URL) {
try {
await AsyncStorage.setItem('base_url', fullTenancy);
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('password', password);
} catch (e) {
console.error('Failed to save login credentials to AsyncStorage', e);
}
await dispatch(login(payload));
} else {
setLocalError('Invalid tenancy name');
console.log('baseUrl set to-', fullTenancy);
try {
await AsyncStorage.setItem('base_url', fullTenancy);
await AsyncStorage.setItem('tenancy_name', cleanTenancy);
await AsyncStorage.setItem('email', email);
await AsyncStorage.setItem('password', password);
} catch (e) {
console.error('Failed to save login credentials to AsyncStorage', e);
}
await dispatch(login(payload));
};
useEffect(() => {
@ -123,15 +124,13 @@ export const LoginScreen = () => {
useEffect(() => {
if (loginSuccess && token && user_data) {
// Send FCM token to server immediately after login
if (deviceToken && user_data.staffid) {
dispatch(sendFcmToken({ id: user_data.staffid, fcm_token: deviceToken }));
}
// if (deviceToken && user_data.staffid) {
// dispatch(sendFcmToken({ id: user_data.staffid, fcm_token: deviceToken }));
// }
navigation.reset({
index: 0,
routes: [{ name: 'DrawerStack' }],
});
// Show permission popup after the user has landed in the app
setTimeout(() => NotificationService.requestPermission(), 1000);
}
}, [loginSuccess, token, user_data, navigation]);

View File

@ -1,105 +1,98 @@
import React, { useLayoutEffect, useState } from 'react';
import React, { useEffect, useLayoutEffect } from 'react';
import {
View,
Text,
FlatList,
TouchableOpacity,
StatusBar,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import Icon from 'react-native-vector-icons/Ionicons';
import { useTheme } from '../../theme';
import { useTheme } from '@theme';
import { getStyles } from './notification.styles';
import { INITIAL_NOTIFICATIONS } from '@mock-data';
import { useAppDispatch, useAppSelector, RootState } from '@store';
import { getStaffNotifications, markNotificationRead, clearNotifications } from './thunk';
import { NotificationItem as NotificationItemType } from '@interfaces';
import { Loader, NotificationItem } from '@components';
export const NotificationScreen = () => {
const dispatch = useAppDispatch();
const navigation = useNavigation();
const { theme: colors } = useTheme();
const styles = getStyles(colors);
const [notifications, setNotifications] = useState(INITIAL_NOTIFICATIONS);
const { items, loading, error } = useAppSelector(
(state: RootState) => state.notifications,
);
const user_data = useAppSelector((state: RootState) => state.auth.user_data);
// Mark all as read header button action
const handleMarkAllRead = () => {
setNotifications(prev =>
prev.map(item => ({ ...item, unread: false }))
);
};
useEffect(() => {
if (user_data?.staffid) {
dispatch(getStaffNotifications(user_data.staffid));
}
}, [dispatch, user_data]);
useLayoutEffect(() => {
navigation.setOptions({
headerTitle: 'Notifications',
headerRight: () => {
const hasUnread = notifications.some(n => n.unread);
const hasUnread = items.some(n => String(n.isread) === '0');
if (!hasUnread) return null;
return (
<TouchableOpacity
style={styles.headerRightBtn}
onPress={handleMarkAllRead}
onPress={() => {
if (user_data?.staffid) {
dispatch(clearNotifications(user_data.staffid));
}
}}
activeOpacity={0.7}
>
<Text style={styles.clearAllText}>Read All</Text>
<Text style={styles.clearAllText}>Clear All</Text>
</TouchableOpacity>
);
},
});
}, [navigation, notifications, styles]);
}, [navigation, items, styles]);
const toggleSingleRead = (id: string) => {
setNotifications(prev =>
prev.map(item =>
item.id === id ? { ...item, unread: !item.unread } : item
)
);
const handleNotificationPress = (item: NotificationItemType) => {
if (String(item.isread) === '0' && user_data?.staffid) {
dispatch(
markNotificationRead({
staffId: user_data.staffid,
notificationId: item.id,
}),
);
}
// Note: You can add navigation logic here if `item.link` is provided
};
const renderItem = ({ item }: { item: typeof INITIAL_NOTIFICATIONS[0] }) => {
const renderItem = ({ item }: { item: NotificationItemType }) => {
return <NotificationItem item={item} onPress={handleNotificationPress} />;
};
if (loading) {
return <Loader message="Loading notifications..." />;
}
if (error) {
return (
<TouchableOpacity
style={[styles.card, item.unread && styles.unreadCard]}
activeOpacity={0.7}
onPress={() => toggleSingleRead(item.id)}
>
{/* Left Side Styled Icon Circle */}
<View style={[styles.iconWrapper, { backgroundColor: `${item.iconColor}12` }]}>
<Icon name={item.iconName} size={18} color={item.iconColor} />
</View>
{/* Content Section */}
<View style={styles.contentContainer}>
<View style={styles.titleRow}>
<Text style={styles.title} numberOfLines={1}>
{item.title}
</Text>
{item.unread && <View style={styles.unreadDot} />}
</View>
<Text style={styles.description}>{item.description}</Text>
<Text style={styles.time}>{item.time}</Text>
</View>
</TouchableOpacity>
<View style={styles.emptyContainer}>
<Icon name="alert-circle-outline" size={48} color={colors.textMuted} />
<Text style={styles.emptyTitle}>{error}</Text>
{/* <Text style={styles.emptySubtitle}>{error}</Text> */}
</View>
);
};
}
return (
<View style={styles.container}>
{/* <StatusBar barStyle="light-content" /> */}
{notifications.length > 0 ? (
<FlatList
data={notifications}
data={items}
keyExtractor={item => item.id}
renderItem={renderItem}
contentContainerStyle={styles.listContent}
showsVerticalScrollIndicator={false}
/>
) : (
<View style={styles.emptyContainer}>
<Icon name="notifications-off-outline" size={48} color={colors.textMuted} />
<Text style={styles.emptyTitle}>All caught up!</Text>
<Text style={styles.emptySubtitle}>
When you get new updates or alerts, they will show up here.
</Text>
</View>
)}
</View>
);
};

View File

@ -0,0 +1,63 @@
import { createReducer } from '@reduxjs/toolkit';
import { getStaffNotifications, markNotificationRead, clearNotifications } from './thunk';
import { NotificationItem } from '@interfaces';
export interface NotificationState {
items: NotificationItem[];
loading: boolean;
error: string | null;
hasFetched: boolean;
}
const initialState: NotificationState = {
items: [],
loading: false,
error: null,
hasFetched: false,
};
export const reducers = createReducer(initialState, builder => {
builder
.addCase(getStaffNotifications.pending, acc => {
acc.loading = true;
acc.error = null;
})
.addCase(getStaffNotifications.fulfilled, (acc, action) => {
acc.loading = false;
acc.items = action.payload;
acc.error = null;
acc.hasFetched = true;
})
.addCase(getStaffNotifications.rejected, (acc, action) => {
acc.loading = false;
acc.hasFetched = true;
acc.error =
(action.payload as string) ??
action.error.message ??
'Failed to load notifications';
})
.addCase(markNotificationRead.pending, (acc, action) => {
// Optimistic update: mark as read immediately
const item = acc.items.find(i => i.id === action.meta.arg.notificationId);
if (item) {
item.isread = '1';
}
})
.addCase(markNotificationRead.rejected, (acc, action) => {
// If it fails, revert it (simple approach: you could also re-fetch)
const item = acc.items.find(i => i.id === action.meta.arg.notificationId);
if (item) {
item.isread = '0';
}
acc.error = (action.payload as string) ?? 'Failed to mark notification as read';
})
.addCase(clearNotifications.pending, (acc) => {
// Optimistic update: clear items immediately
acc.items = [];
})
.addCase(clearNotifications.rejected, (acc, action) => {
acc.error = (action.payload as string) ?? 'Failed to clear notifications';
});
});
export default reducers;

View File

@ -0,0 +1,49 @@
import { createAsyncThunk } from '@reduxjs/toolkit';
import { getStaffNotificationsApi, markNotificationReadApi, clearNotificationsApi } from '@api';
import { NotificationItem } from '@interfaces';
export const getStaffNotifications = createAsyncThunk<
NotificationItem[],
string
>('notification/getStaffNotifications', async (staffId, { rejectWithValue }) => {
try {
return await getStaffNotificationsApi(staffId);
} catch (error: any) {
const serverMessage =
error?.response?.data?.message ||
error.message ||
'Failed to load notifications';
return rejectWithValue(serverMessage);
}
});
export const markNotificationRead = createAsyncThunk<
{ id: string },
{ staffId: string; notificationId: string }
>('notification/markNotificationRead', async (payload, { rejectWithValue }) => {
try {
await markNotificationReadApi(payload.staffId, payload.notificationId);
return { id: payload.notificationId };
} catch (error: any) {
const serverMessage =
error?.response?.data?.message ||
error.message ||
'Failed to mark notification as read';
return rejectWithValue(serverMessage);
}
});
export const clearNotifications = createAsyncThunk<
void,
string
>('notification/clearNotifications', async (staffId, { rejectWithValue }) => {
try {
await clearNotificationsApi(staffId);
} catch (error: any) {
const serverMessage =
error?.response?.data?.message ||
error.message ||
'Failed to clear notifications';
return rejectWithValue(serverMessage);
}
});

View File

@ -6,6 +6,7 @@ import {
ScrollView,
Switch,
Image,
Alert,
} from 'react-native';
import { useNavigation } from '@react-navigation/native';
import Icon from 'react-native-vector-icons/Ionicons';
@ -13,6 +14,7 @@ import { getStyles } from './profile.styles';
import { useTheme } from '../../theme';
import { ProfileInfoRow } from '@components';
import { useAppDispatch, useAppSelector, logout, RootState } from '@store';
import { NotificationService } from '@services';
export const ProfileScreen = () => {
const navigation = useNavigation<any>();
@ -25,14 +27,25 @@ export const ProfileScreen = () => {
const handleLogout = async () => {
try {
dispatch(logout());
} catch (e) {
console.error(e);
const fcmToken = await NotificationService.getFCMToken();
console.log('fcmToken', fcmToken)
await dispatch(
logout({
id: userData?.staffid ?? '',
fcm_token: fcmToken ?? '',
}),
).unwrap();
navigation.reset({
index: 0,
routes: [{ name: 'AuthStack' }],
});
} catch (e: any) {
const message =
typeof e === 'string'
? e
: e?.message || 'Logout failed. Please try again.';
Alert.alert('Logout Failed', message);
}
navigation.reset({
index: 0,
routes: [{ name: 'AuthStack' }],
});
};
// Derive user info

View File

@ -1,7 +1,8 @@
export interface LoginRequest {
email: string;
password: string;
device_token?: string;
fcm_token?: string;
device_id?: string;
}
export interface Permission {
@ -55,3 +56,13 @@ export interface LoginResponse {
user_data: UserData;
token: string;
}
export interface LogoutRequest {
id: string;
fcm_token: string;
}
export interface LogoutResponse {
status: boolean;
message: string;
}

View File

@ -0,0 +1,15 @@
export interface DashboardLeadCountItem {
id: string;
name: string;
statusorder: number | string;
color: string;
isdefault: number | string;
count: number;
}
export type DashboardLeadCountAction = 'day' | 'week' | 'month' | 'quarter' | 'year';
export interface DashboardLeadCountPayload {
action: DashboardLeadCountAction | string;
staffid: string | number;
}

View File

@ -5,4 +5,7 @@ export * from './leadDetails';
export * from './list';
export * from './customers';
export * from './fcmToken';
export * from './notification';
export * from './dashboardLeadCount';

View File

@ -0,0 +1,27 @@
export interface NotificationItem {
id: string;
isread: string;
is_hide: string;
isread_inline: string;
date: string;
description: string;
fromuserid: string;
fromclientid: string;
from_fullname: string;
touserid: string;
fromcompany: string | null;
link: string;
additional_data: string;
profile_image: string;
full_date: string;
}
export interface MarkNotificationReadResponse {
status: boolean;
message: string;
}
export interface ClearNotificationsResponse {
status: boolean;
message: string;
}

View File

@ -5,6 +5,7 @@ import {
TouchableOpacity,
ScrollView,
Image,
Alert,
} from 'react-native';
import { DrawerContentComponentProps } from '@react-navigation/drawer';
import Icon from 'react-native-vector-icons/Ionicons';
@ -14,6 +15,7 @@ import { DrawerItemProps } from '@interfaces';
import { route } from '@utils';
import { getStyles } from './customDrawerContent.style';
import { useAppDispatch, useAppSelector, logout, RootState } from '@store';
import { NotificationService } from '@services';
const DrawerItem = ({ label, iconName, focused, onPress }: DrawerItemProps) => {
@ -49,14 +51,24 @@ export const CustomDrawerContent = (props: DrawerContentComponentProps) => {
const handleLogout = async () => {
try {
dispatch(logout());
} catch (e) {
console.error(e);
const fcmToken = await NotificationService.getFCMToken();
await dispatch(
logout({
id: userData?.staffid ?? '',
fcm_token: fcmToken ?? '',
}),
).unwrap();
navigation.reset({
index: 0,
routes: [{ name: 'AuthStack' }],
});
} catch (e: any) {
const message =
typeof e === 'string'
? e
: e?.message || 'Logout failed. Please try again.';
Alert.alert('Logout Failed', message);
}
navigation.reset({
index: 0,
routes: [{ name: 'AuthStack' }],
});
};
const activeRouteName = state.routeNames[state.index];
@ -89,7 +101,10 @@ export const CustomDrawerContent = (props: DrawerContentComponentProps) => {
<TouchableOpacity
style={styles.userHeaderTouchable}
activeOpacity={0.8}
onPress={() => navigation.navigate('home', { screen: route.profile })}>
onPress={() => {
navigation.navigate('home', { screen: route.profile });
navigation.closeDrawer();
}}>
<View style={styles.avatarWrapper}>
{hasProfileImage ? (
<Image
@ -154,6 +169,7 @@ export const CustomDrawerContent = (props: DrawerContentComponentProps) => {
} else {
navigation.navigate('home', { screen: item.route });
}
navigation.closeDrawer();
}}
/>
);

View File

@ -33,6 +33,7 @@ export const CustomersStack = () => {
headerTitleAlign: 'center',
headerTintColor: colors.text,
headerShadowVisible: false,
statusBarTranslucent: false,
}}>
<Stack.Screen
name={route.customersList}

View File

@ -39,6 +39,7 @@ export const LeadsStack = () => {
headerTitleAlign: 'center',
headerTintColor: colors.text,
headerShadowVisible: false,
statusBarTranslucent: false,
}}>
<Stack.Screen
name={route.leadsList}

View File

@ -29,7 +29,12 @@ const DashboardStack = () => {
const { theme: colors } = useTheme();
const styles = getStyles(colors);
const userData = useAppSelector((state: RootState) => state.auth.user_data);
const unreadCount = parseInt(userData?.total_unread_notifications ?? '0', 10);
const { items: notificationItems, hasFetched } = useAppSelector((state: RootState) => state.notifications);
// Calculate unread count from actual items if they've been fetched, otherwise fallback to userData's initial count
const unreadCount = hasFetched
? notificationItems.filter(n => String(n.isread) === '0').length
: parseInt(userData?.total_unread_notifications ?? '0', 10);
return (
<DashboardStackNav.Navigator
@ -45,6 +50,7 @@ const DashboardStack = () => {
headerTitleAlign: 'center',
headerTintColor: colors.text,
headerShadowVisible: false,
statusBarTranslucent: false,
}}>
<DashboardStackNav.Screen
name="dashboardList"
@ -115,6 +121,7 @@ const AddLeadStack = () => {
headerTitleAlign: 'center',
headerTintColor: colors.text,
headerShadowVisible: false,
statusBarTranslucent: false,
}}>
<AddLeadStackNav.Screen
name="addLeadForm"
@ -160,6 +167,7 @@ const ProfileStack = () => {
headerTitleAlign: 'center',
headerTintColor: colors.text,
headerShadowVisible: false,
statusBarTranslucent: false,
}}>
<ProfileStackNav.Screen
name="profileForm"

View File

@ -1,6 +1,6 @@
import { createAction, createAsyncThunk } from '@reduxjs/toolkit';
import { LoginRequest, LoginResponse } from '@interfaces';
import { loginApi } from '@api';
import { LoginRequest, LoginResponse, LogoutRequest, LogoutResponse } from '@interfaces';
import { loginApi, logoutApi } from '@api';
export const login = createAsyncThunk<LoginResponse, LoginRequest>(
'auth/login',
@ -8,9 +8,30 @@ export const login = createAsyncThunk<LoginResponse, LoginRequest>(
try {
return await loginApi(payload);
} catch (error: any) {
return rejectWithValue(error.message || 'Failed to login');
const serverMessage =
error?.response?.data?.message ||
error.message ||
'Failed to login';
return rejectWithValue(serverMessage);
}
},
);
export const logout = createAction('USER_LOGOUT');
export const clearLocalAuth = createAction('USER_LOGOUT');
export const logout = createAsyncThunk<LogoutResponse, LogoutRequest>(
'auth/logout',
async (payload, { dispatch, rejectWithValue }) => {
try {
const response = await logoutApi(payload);
dispatch(clearLocalAuth());
return response;
} catch (error: any) {
const serverMessage =
error?.response?.data?.message ||
error.message ||
'Failed to logout';
return rejectWithValue(serverMessage);
}
},
);

View File

@ -12,6 +12,8 @@ import addLeadReducer from '../features/addLead/reducers';
import customersReducer from '../features/customers/reducers';
import customerDetailsReducer from '../features/customerDetails/reducers';
import addCustomerReducer from '../features/addCustomer/reducers';
import notificationReducer from '../features/notification/reducers';
import dashboardReducer from '../features/dashboard/reducers';
const appReducer = combineReducers({
auth: authReducer,
@ -27,6 +29,8 @@ const appReducer = combineReducers({
customers: customersReducer,
customerDetails: customerDetailsReducer,
addCustomer: addCustomerReducer,
notifications: notificationReducer,
dashboard: dashboardReducer,
});
const rootReducer = (state: any, action: any) => {

View File

@ -1,9 +1,9 @@
import axios, { AxiosInstance, AxiosRequestConfig, AxiosError } from 'axios';
import config from 'react-native-config';
import AsyncStorage from '@react-native-async-storage/async-storage';
import {Store} from '@reduxjs/toolkit';
const axiosInstance: AxiosInstance = axios.create({
baseURL: config.BASE_URL,
timeout: 15000,
headers: {
'Content-Type': 'application/json',
@ -12,7 +12,13 @@ const axiosInstance: AxiosInstance = axios.create({
});
axiosInstance.interceptors.request.use(
request => {
async request => {
// Dynamically set baseURL from AsyncStorage on every request
const storedBaseUrl = await AsyncStorage.getItem('base_url');
if (storedBaseUrl) {
request.baseURL = storedBaseUrl;
}
if (request.data instanceof FormData) {
delete request.headers?.['Content-Type'];
}
@ -28,9 +34,10 @@ axiosInstance.interceptors.response.use(
if (error.response) {
const status = error.response.status;
const data = error.response.data;
console.error('[API] response error', status, data);
// Use warn instead of error to avoid triggering the RN dev overlay
console.warn('[API] response error', status, data);
} else {
console.error('[API] network error', error.message);
console.warn('[API] network error', error.message);
}
return Promise.reject(error);

View File

@ -21,14 +21,19 @@
"@react-navigation/stack": "^7.10.11",
"@reduxjs/toolkit": "^2.12.0",
"axios": "^1.18.1",
"dayjs": "^1.11.21",
"react": "19.2.3",
"react-native": "0.86.0",
"react-native-bootsplash": "^7.3.2",
"react-native-config": "^1.6.1",
"react-native-device-info": "^15.0.2",
"react-native-gesture-handler": "^2.32.0",
"react-native-gifted-charts": "^1.4.77",
"react-native-linear-gradient": "^2.8.3",
"react-native-reanimated": "^4.5.0",
"react-native-safe-area-context": "^5.8.0",
"react-native-screens": "^4.25.2",
"react-native-svg": "^15.15.5",
"react-native-vector-icons": "^10.3.0",
"react-native-worklets": "^0.10.2",
"react-redux": "^9.3.0",

448
yarn.lock
View File

@ -1034,16 +1034,16 @@
"@types/hammerjs" "^2.0.36"
"@emnapi/runtime@^1.11.1":
version "1.11.2"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.2.tgz#eb22f04d76febfdf4f87fdaff54c8a53f6bf0dbd"
integrity sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==
version "1.11.3"
resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-1.11.3.tgz#84257ae3b0531eb2aec1ffa23d70700da007ba95"
integrity sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==
dependencies:
tslib "^2.4.0"
"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.9.1":
version "4.9.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz#4e90af67bc51ddee6cdef5284edf572ec376b595"
integrity sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==
version "4.10.1"
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz#8911bd72b2c3640a543609e0400b8c4d2e7e7cb6"
integrity sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==
dependencies:
eslint-visitor-keys "^3.4.3"
@ -1073,14 +1073,14 @@
integrity sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==
"@expo/config-plugins@*":
version "57.0.5"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-57.0.5.tgz#6bbaeaef5d60218bee7afa9d05cc9427d29dfe8c"
integrity sha512-xhUGgzpFWRghDUH98+Wl4RDakYhTsbyMg6aOYiBjRzPO/THH8tKMw3vlksgFYlU2PkiAdABJN3tNPf5qmvOQhA==
version "57.0.6"
resolved "https://registry.yarnpkg.com/@expo/config-plugins/-/config-plugins-57.0.6.tgz#5ce6b6fda683133899678602aa8615209601ece5"
integrity sha512-7CmKrS5Rnu8aSZyNlxH2qzA7Ls1HEa4EQvEVOAkHDKPr1e4Cg/nz7I7dUl09QDTVjMvkKYDH4Th0DuAsgqASaw==
dependencies:
"@expo/config-types" "^57.0.2"
"@expo/json-file" "~11.0.1"
"@expo/plist" "^0.8.1"
"@expo/require-utils" "^57.0.3"
"@expo/require-utils" "^57.0.4"
"@expo/sdk-runtime-versions" "^1.0.0"
chalk "^4.1.2"
debug "^4.3.5"
@ -1113,10 +1113,10 @@
base64-js "^1.5.1"
xmlbuilder "^15.1.1"
"@expo/require-utils@^57.0.3":
version "57.0.3"
resolved "https://registry.yarnpkg.com/@expo/require-utils/-/require-utils-57.0.3.tgz#b2bcac04f0f227aad8412ac3a78a6a3b5c26093f"
integrity sha512-ns05X1K8tM+Qtzp6dNloUFOopSdh3J+HC61BtOR8WHhgtPFyX8TKuO2diqZUqVg9K8yfkWug7g8tBS0qRniSTA==
"@expo/require-utils@^57.0.4":
version "57.0.4"
resolved "https://registry.yarnpkg.com/@expo/require-utils/-/require-utils-57.0.4.tgz#fcf4377a64a21a70c00c787a9b8fc95ac438cc79"
integrity sha512-e7xbg/9BTQcsZE/oErafZXtI7kh5IgfasLJ97J5sFSzX2cA74pDvdlhW1KHVSaDkQyQv6h1LSLhsY7dEeOk7hw==
dependencies:
"@babel/code-frame" "^7.20.0"
"@babel/core" "^7.25.2"
@ -2419,20 +2419,20 @@
nullthrows "^1.1.1"
"@react-navigation/bottom-tabs@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.8.tgz#a611371cbe01f6a84873e1725c269c8746e4510f"
integrity sha512-7KCsBtwCRwQQSTEw9SLglZCEkxWc+EqgdRKyUOgII+6+xEnemOyg8aDlnLIM2yk9ip6uE+Oxvm0jdpQU2vsu2w==
version "7.18.14"
resolved "https://registry.yarnpkg.com/@react-navigation/bottom-tabs/-/bottom-tabs-7.18.14.tgz#24fb78948fb06ef7a19b4ff0d596c1d6d643bdbf"
integrity sha512-A3V9rDSut459TBPtkD7rb0npUUBlJBfMunyRT5nOGKqPguhuXWk1h91NfQMuqyW2DCUvvFZChzsbLbj42rXTdQ==
dependencies:
"@react-navigation/elements" "^2.9.30"
"@react-navigation/elements" "^2.9.36"
color "^4.2.3"
sf-symbols-typescript "^2.1.0"
"@react-navigation/core@^7.21.5":
version "7.21.5"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.21.5.tgz#2f2c7f5374c9a20d942688aa37c0e9f6665a9549"
integrity sha512-3hpV7uR41LBW+GHDoLhztZCb/i5ySRJISZ/rez4d7DCHSZo6ej4gNxYclaS6LRguoLiKG7SOCNa6O390AQklZQ==
"@react-navigation/core@^7.21.11":
version "7.21.11"
resolved "https://registry.yarnpkg.com/@react-navigation/core/-/core-7.21.11.tgz#dfc9c85f297755fe94c69b34b94df0982c4bf45f"
integrity sha512-bCW1PsLA/eOXDOukcJFEzlcL3Zpy8DJuDCfkDDwAQlAgoSZ/J9+ZeDRUMmCUi6xbnFgvFEEIMertaLeErOFP0Q==
dependencies:
"@react-navigation/routers" "^7.6.0"
"@react-navigation/routers" "^7.6.4"
escape-string-regexp "^4.0.0"
fast-deep-equal "^3.1.3"
nanoid "^3.3.11"
@ -2442,59 +2442,59 @@
use-sync-external-store "^1.5.0"
"@react-navigation/drawer@^7.12.8":
version "7.12.8"
resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-7.12.8.tgz#aa321d5959a31db661c5c24ec5f544f5d0899d65"
integrity sha512-EY4ItjflOGxY2d73L6+LZe4sMQSJGrqbU/pBBR32unR7KP9Crb39m0tpBdtnGHMV18XGh/I6nPVYS3+y/7LaNw==
version "7.13.5"
resolved "https://registry.yarnpkg.com/@react-navigation/drawer/-/drawer-7.13.5.tgz#54c5fc4065fd12cf9687b0407f4d8145dc2c9e92"
integrity sha512-e1WdwyXeCS/tUDFSTIlNLgOWyUdz4hZnxQrAF7Ia1jY8F6gLNgo+Q04kxbUvTqS4wXZYfG2DSW94WnJogR3oSQ==
dependencies:
"@react-navigation/elements" "^2.9.30"
"@react-navigation/elements" "^2.9.36"
color "^4.2.3"
react-native-drawer-layout "^4.2.7"
react-native-drawer-layout "^4.2.9"
use-latest-callback "^0.2.4"
"@react-navigation/elements@^2.9.30":
version "2.9.30"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.30.tgz#60702877319f06c20b15e3f98446dcc994c67cff"
integrity sha512-2isleieiRMmP4WNMV2Q1u3qP1M47ZqsJ2hJ/Og11FeKXK8YmUTHya7PW7ecsgAh2CXKTxAcbfJDFTSvq2D++Tw==
"@react-navigation/elements@^2.9.36":
version "2.9.36"
resolved "https://registry.yarnpkg.com/@react-navigation/elements/-/elements-2.9.36.tgz#b060502ed97768f719b604dc9b652031babded1a"
integrity sha512-+10x9s5v2Q7FwAYdSmPMgILtxZyC5e4hWJQu8g5o3u4p8DUToTBmGvys/UvmEr+h9xmm0Go42qw9Ff2ape53kQ==
dependencies:
color "^4.2.3"
use-latest-callback "^0.2.4"
use-sync-external-store "^1.5.0"
"@react-navigation/native-stack@^7.17.10":
version "7.17.10"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.17.10.tgz#d838eac41f4cbd58ba18ce542397db80962378b0"
integrity sha512-m1BWVEaOPX9k30DbmhsD7IlrUdl4J7Ogmo71rcNlbZQPMNB126av/nfwqB+qN7DIsiwTAnORnT+SwzD56qqD0Q==
version "7.18.6"
resolved "https://registry.yarnpkg.com/@react-navigation/native-stack/-/native-stack-7.18.6.tgz#71ed9d2a28368bd46d8dded050dbffd9d818f090"
integrity sha512-KuvvSBddHrbKC4c6yKz+UCFey2xgTuPQHgjf2wJQAvA7JM8jCQa3G1+QzhO6d44nYNKir3y6EounXZvQE/BX6w==
dependencies:
"@react-navigation/elements" "^2.9.30"
"@react-navigation/elements" "^2.9.36"
color "^4.2.3"
sf-symbols-typescript "^2.1.0"
warn-once "^0.1.1"
"@react-navigation/native@^7.3.8":
version "7.3.8"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.3.8.tgz#01513734e93ac40fd3b48fd6169836fbf5678ac8"
integrity sha512-zHmQcxWBT8GOwsofEOmHqpdM5twkwE/esa9JFGlW4hpXeQTTe/dRcPSLjwsvePtolbDKz0YZbK+I5KrW3j63LQ==
version "7.3.14"
resolved "https://registry.yarnpkg.com/@react-navigation/native/-/native-7.3.14.tgz#d846d80b7036fad3a1f1a2f37aa69e020eebe145"
integrity sha512-hcKTDNBuuAA1/xW6QeKYmMPVhk5W9dKGQpPmn5dQeeePwMpu5OZ14NOgwKH0w9D3tg2jupojTcVL0tsx5DTFXg==
dependencies:
"@react-navigation/core" "^7.21.5"
"@react-navigation/core" "^7.21.11"
escape-string-regexp "^4.0.0"
fast-deep-equal "^3.1.3"
nanoid "^3.3.11"
standard-navigation "^0.0.7"
standard-navigation "^0.0.8"
use-latest-callback "^0.2.4"
"@react-navigation/routers@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.6.0.tgz#04ee630c4ecbdfa3c8ef65000909aa2dbf0ff78f"
integrity sha512-lblhDXfS75jLc7G2K7BZGM+7cjqQXk13X/MA4fq/12r62zM+fBhhreLzYflSitrDDXFRJpSvJXy0ziiGU04Xow==
"@react-navigation/routers@^7.6.4":
version "7.6.4"
resolved "https://registry.yarnpkg.com/@react-navigation/routers/-/routers-7.6.4.tgz#7b0dfa19f9fef0ab1588c9dbfc7f7616fced3ff8"
integrity sha512-GI7eJm8/KsZUQaYcXvEExikKurRZRgEsSzyZ7faENfi65yqJBCXjDMwyN1pF6pNW1MoLH1ErDwDivFxY6BzD3w==
dependencies:
nanoid "^3.3.11"
"@react-navigation/stack@^7.10.11":
version "7.10.11"
resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-7.10.11.tgz#75788547aa8c3f43c20cdb87c06342a39f2fb734"
integrity sha512-FNlylwYaGwGArRjmJhmL3FviTjT/j10UVx6TAaqqeLeYEircqMm2kBBz2e9FpcXH2OeCnPinAjZ0B1th2mNmsg==
version "7.10.17"
resolved "https://registry.yarnpkg.com/@react-navigation/stack/-/stack-7.10.17.tgz#628fb539bf7460fa93c72d36cfbfcc4988752d35"
integrity sha512-2b/AC8YxfPkFsN8DZGjrMiX6G86unbEonE0E8dA5LugzfKF/s28VBM7yBwZKRsp3ys5aVCvt/Z3zviQA/uGAIA==
dependencies:
"@react-navigation/elements" "^2.9.30"
"@react-navigation/elements" "^2.9.36"
color "^4.2.3"
use-latest-callback "^0.2.4"
@ -2528,9 +2528,9 @@
integrity sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==
"@sinclair/typebox@^0.27.8":
version "0.27.10"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.10.tgz#beefe675f1853f73676aecc915b2bd2ac98c4fc6"
integrity sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==
version "0.27.12"
resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.12.tgz#0cacd3cff047a32936b1ace47ea7c86eaab60a7f"
integrity sha512-hhyNJ+nbR6ZR7pToHvllEFun9TL0sbL+tk/ON75lo+Xas054uez98qRbsuNt7MBCyZKK4+8Yli/OAGZhmfBZ/g==
"@sinonjs/commons@^3.0.0":
version "3.0.1"
@ -2629,9 +2629,9 @@
pretty-format "^29.0.0"
"@types/node@*", "@types/node@>=12.12.47", "@types/node@>=13.7.0":
version "26.1.1"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.1.tgz#bad758d601e97d6cf457d204ee76a35fce7bd119"
integrity sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==
version "26.1.2"
resolved "https://registry.yarnpkg.com/@types/node/-/node-26.1.2.tgz#da79708f1f9c6294f4cdec8f455a3032b028808a"
integrity sha512-Vu4a5UFA9rIIFJ7rB/Vaafh9lrCQszopTCx6KjFboXTGQbPNasehVR5TEiithSDGyd1DEiUByggTZsg8jukeIg==
dependencies:
undici-types "~8.3.0"
@ -2672,99 +2672,99 @@
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^8.36.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.64.0.tgz#71a0c3d5f8a5e6c5dfdb4f0f04bd1bfb572d5e24"
integrity sha512-CGvQPBxN3wZLu6Rz2kFUpZeoCm78xUic92ck39KPePkO1NPOwjCqdQnm5Q87tpWw9vcBvW8XLrDXjH9PWYtJ3Q==
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.65.0.tgz#0a58df6fea8c0bf6b396f518077099bc8b762bb5"
integrity sha512-IEgob78X12rHpUmtcwFsXhZdVGJtwTVP8FiCLZkR6GlYVrl2PcuB+KhCE5BlVC/eQpQnu8WXRtkHZuPar+gCRA==
dependencies:
"@eslint-community/regexpp" "^4.12.2"
"@typescript-eslint/scope-manager" "8.64.0"
"@typescript-eslint/type-utils" "8.64.0"
"@typescript-eslint/utils" "8.64.0"
"@typescript-eslint/visitor-keys" "8.64.0"
"@typescript-eslint/scope-manager" "8.65.0"
"@typescript-eslint/type-utils" "8.65.0"
"@typescript-eslint/utils" "8.65.0"
"@typescript-eslint/visitor-keys" "8.65.0"
ignore "^7.0.5"
natural-compare "^1.4.0"
ts-api-utils "^2.5.0"
"@typescript-eslint/parser@^8.36.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.64.0.tgz#c9864a1cc28a13ff29a7314fbdef0528bb122f72"
integrity sha512-KA0OshtlcCCXmbfqyZkM5pV3/WNraJf7DkJRLpyrmwPtud57H5BDX7C3k0LPSPxpprfRL+cJDGabF10mvNCoCw==
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.65.0.tgz#5295c1058c0a1dd746ef28baaf9c0341dbdf03dc"
integrity sha512-CZ4nMxWwgu1HEEFNkeaCptra9QCtkmKdgf3sWh1rl1trIhmxLilgTV4cwcbQ4wemnT4sWQN8CaKOmdYx+g2gMA==
dependencies:
"@typescript-eslint/scope-manager" "8.64.0"
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/typescript-estree" "8.64.0"
"@typescript-eslint/visitor-keys" "8.64.0"
"@typescript-eslint/scope-manager" "8.65.0"
"@typescript-eslint/types" "8.65.0"
"@typescript-eslint/typescript-estree" "8.65.0"
"@typescript-eslint/visitor-keys" "8.65.0"
debug "^4.4.3"
"@typescript-eslint/project-service@8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.64.0.tgz#14c4e29390d7325a7f8a1218c2788fd649b85da6"
integrity sha512-tk4WpOJ6IEbGrVHaNmM0YRrwAD3exZlIK3iadQNAxh4YKk6jvUQ4ecq18n+v7+meh+cJ3j+D8nbk8sRKhlwLQg==
"@typescript-eslint/project-service@8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/project-service/-/project-service-8.65.0.tgz#65fbbc9a1591abffaeab5513200f848271cb0aa5"
integrity sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==
dependencies:
"@typescript-eslint/tsconfig-utils" "^8.64.0"
"@typescript-eslint/types" "^8.64.0"
"@typescript-eslint/tsconfig-utils" "^8.65.0"
"@typescript-eslint/types" "^8.65.0"
debug "^4.4.3"
"@typescript-eslint/scope-manager@8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.64.0.tgz#d45f15304a94c85c39db317b717b158fb6259958"
integrity sha512-CXEaFdYXjSTgKhisNkwCcJwTP8Pl+fmRrEQrri4nm3vU743bALrxzLmq7fHG/7e6a5xO0lDYeURpZmBuhHk54w==
"@typescript-eslint/scope-manager@8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz#9547202ce7e608e7b6283df585703b980a0ea70d"
integrity sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==
dependencies:
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/visitor-keys" "8.64.0"
"@typescript-eslint/types" "8.65.0"
"@typescript-eslint/visitor-keys" "8.65.0"
"@typescript-eslint/tsconfig-utils@8.64.0", "@typescript-eslint/tsconfig-utils@^8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.64.0.tgz#c62ac8ea9173c3cac8b38b8e66e30a046b548851"
integrity sha512-2yo8rRNKuzbVWQp5kslhANqZ2uDAeROQHBRZNPu8JDsHmeFNj/XJJhX/FhNUWmkHHvoNsKa6+tHJiig87EzsQw==
"@typescript-eslint/tsconfig-utils@8.65.0", "@typescript-eslint/tsconfig-utils@^8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz#36f168fcdbb1295f7446ff0379667f98c3cf1bf3"
integrity sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==
"@typescript-eslint/type-utils@8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.64.0.tgz#106fa7d58cf9cf7758f3dd8e426ac8237eceacf3"
integrity sha512-XWG4Fmmv/6SvyS9nH8jWrKs6terwJvE8cyRt1CzYYqzp9OrPhCT4cMc/f7C6RZCwG+qMmiffJS1/qJP8G1URtg==
"@typescript-eslint/type-utils@8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.65.0.tgz#d316d7522d93cff4cd14f305e02f3df2d804f9c1"
integrity sha512-YjaZ7PRI5qY7ax2L3PbvX0rRyGtipAReCWs0mhhDBHjH/vl0g0BonaGXrKdKpMbIIsMIwDgbk/xzkBTyAltS5g==
dependencies:
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/typescript-estree" "8.64.0"
"@typescript-eslint/utils" "8.64.0"
"@typescript-eslint/types" "8.65.0"
"@typescript-eslint/typescript-estree" "8.65.0"
"@typescript-eslint/utils" "8.65.0"
debug "^4.4.3"
ts-api-utils "^2.5.0"
"@typescript-eslint/types@8.64.0", "@typescript-eslint/types@^8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.64.0.tgz#b41f8ef5dd40616908658b991197a9d486cda60b"
integrity sha512-qjhfuTfLXjA4IOzXvz0rTjT01BqEiIgPoUeMwiEjnaHKJMTNo8rH5pYW1a2L/0Dnux2fPC85AeyJoWaGa8WxTA==
"@typescript-eslint/types@8.65.0", "@typescript-eslint/types@^8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.65.0.tgz#3e86738416a777c8b8925ab46745f48ecf904c9f"
integrity sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==
"@typescript-eslint/typescript-estree@8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.64.0.tgz#b8d51255e2d726eb4bd80d397a4fb4170c02eecc"
integrity sha512-Pztpsn1aCE1oWDvDEfUk31nngvvF7vUB5SwHFEaZIFpvw7WJtqUHHL4plBZDA9HfWJJjL13BdG0YrJInTUvoVA==
"@typescript-eslint/typescript-estree@8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz#f1f514808f6aa713e2d678ae8ff592a65e1632af"
integrity sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==
dependencies:
"@typescript-eslint/project-service" "8.64.0"
"@typescript-eslint/tsconfig-utils" "8.64.0"
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/visitor-keys" "8.64.0"
"@typescript-eslint/project-service" "8.65.0"
"@typescript-eslint/tsconfig-utils" "8.65.0"
"@typescript-eslint/types" "8.65.0"
"@typescript-eslint/visitor-keys" "8.65.0"
debug "^4.4.3"
minimatch "^10.2.2"
semver "^7.7.3"
tinyglobby "^0.2.15"
ts-api-utils "^2.5.0"
"@typescript-eslint/utils@8.64.0", "@typescript-eslint/utils@^8.0.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.64.0.tgz#98bb2010cfb754b41985b9c93e6e8b3dcd7bd600"
integrity sha512-aJUGVB3+U0htrrCjoA8qukw8cm8fNCGAxK/tVoS70k8aeb7DETKeFozRiVFIwEeN9WJLsjaP3ph8I60tY2XZoQ==
"@typescript-eslint/utils@8.65.0", "@typescript-eslint/utils@^8.0.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.65.0.tgz#afedd974a0c8deeef553b509df5800bafd615a72"
integrity sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==
dependencies:
"@eslint-community/eslint-utils" "^4.9.1"
"@typescript-eslint/scope-manager" "8.64.0"
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/typescript-estree" "8.64.0"
"@typescript-eslint/scope-manager" "8.65.0"
"@typescript-eslint/types" "8.65.0"
"@typescript-eslint/typescript-estree" "8.65.0"
"@typescript-eslint/visitor-keys@8.64.0":
version "8.64.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.64.0.tgz#7a08421d10e54960733352cd7c95fab1784e8473"
integrity sha512-mrtuL8Nsn6gi2H4mo5KMTp823M+3Q19Ew/i+Zlikq20tIMm99C3Ez0dCmkWWnxut20esQvTg8aUSEhMcAOXhEw==
"@typescript-eslint/visitor-keys@8.65.0":
version "8.65.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz#e3704c13cb4a1c22454c1abf28ff4737e15018c6"
integrity sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==
dependencies:
"@typescript-eslint/types" "8.64.0"
"@typescript-eslint/types" "8.65.0"
eslint-visitor-keys "^5.0.0"
"@ungap/structured-clone@^1.2.0":
@ -2816,9 +2816,9 @@ acorn-jsx@^5.3.2:
integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
acorn@^8.15.0, acorn@^8.9.0:
version "8.17.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.17.0.tgz#1785adb84faf8d8add10369b93826fc2bd08f1fe"
integrity sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==
version "8.18.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.18.0.tgz#4faf01b2d6d326bfeed97aea1f52220b5f4c1940"
integrity sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==
agent-base@6:
version "6.0.2"
@ -2901,9 +2901,9 @@ anymatch@^3.0.3:
picomatch "^2.0.4"
appdirsjs@^1.2.4:
version "1.2.7"
resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.7.tgz#50b4b7948a26ba6090d4aede2ae2dc2b051be3b3"
integrity sha512-Quji6+8kLBC3NnBeo14nPDq0+2jUs5s3/xEye+udFHumHhRk4M7aAMXp/PBJqkKYGuuyR9M/6Dq7d2AViiGmhw==
version "1.2.8"
resolved "https://registry.yarnpkg.com/appdirsjs/-/appdirsjs-1.2.8.tgz#8779e70bc4c5aa205d428c037dfb916e6d7c2d69"
integrity sha512-8zl1xlxeS4a0/36CT6LOaVioPOL8TeLT1b9OHk0j9xSbzmPBuM7lUgWMSTh6SbuF8fbwjcP1rr30OCLpd1fl+A==
argparse@^1.0.7:
version "1.0.10"
@ -3028,12 +3028,12 @@ available-typed-arrays@^1.0.7:
possible-typed-array-names "^1.0.0"
axios@^1.18.1:
version "1.18.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.18.1.tgz#d63f9863bcd8938815c86f9e2abd380189d96dfe"
integrity sha512-3nTvFlvpn9Zu/RkHUqtc7/+al4UpRW5az71ap5zccp6e8RAYEzhMTecX8Dz1wWDYrPpUoB1HAQEGEAEvUr7S9g==
version "1.19.0"
resolved "https://registry.yarnpkg.com/axios/-/axios-1.19.0.tgz#ddf864d4c8233c0e6873746ab59361537d05ad39"
integrity sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==
dependencies:
follow-redirects "^1.16.0"
form-data "^4.0.5"
form-data "^4.0.6"
https-proxy-agent "^5.0.1"
proxy-from-env "^2.1.0"
@ -3172,10 +3172,10 @@ base64-js@^1.3.1, base64-js@^1.5.1:
resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.5.1.tgz#1b1b440160a5bf7ad40b650f095963481903930a"
integrity sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==
baseline-browser-mapping@^2.10.42:
version "2.10.43"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz#7b5d11590ce5acdbe4859443e3c940e81ce8c02d"
integrity sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==
baseline-browser-mapping@^2.10.44:
version "2.11.7"
resolved "https://registry.yarnpkg.com/baseline-browser-mapping/-/baseline-browser-mapping-2.11.7.tgz#65b41243f29d2cca7b5d72ca6e9b8285df1ed975"
integrity sha512-APw5YuIQAg6L9w4sHDI6j26DGFJI6RpYOhnkMPdC9lWbkKvsyPHzDsve1yd73lk21yz7Y09Kci8B2Pp9FonzWA==
big-integer@1.6.x:
version "1.6.52"
@ -3229,24 +3229,24 @@ bplist-parser@0.3.1:
big-integer "1.6.x"
brace-expansion@^1.1.7:
version "1.1.16"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.16.tgz#723d3a30c0558c225abc9fc479a73e14e26c3c2f"
integrity sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==
version "1.1.17"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.17.tgz#a375e14e41d672617de69526be02d0d7751a6909"
integrity sha512-w+aeW/mkgM4PyRMOJCgi3fOrTm5Q8QY1OSfn2TO2iuDj3ezIHqejmuxbjfPrqUkgqRew1iqkyAn0tr0ZwHD9+w==
dependencies:
balanced-match "^1.0.0"
concat-map "0.0.1"
brace-expansion@^2.0.1:
version "2.1.2"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.2.tgz#0bba2271feb7d458b0d31ad13625aaa4754431e2"
integrity sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==
version "2.1.3"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-2.1.3.tgz#1bf69aacdf6a4380ca17c284d9f928d4aa6401bc"
integrity sha512-DRdx5neNsG/QXbniLFWi2YmC/68oeOOmKz6zOjVk6ZS1ZLXgLIKqVEc6hWsmkjBbgii0SwaBTcJ5XKj5gzY/4A==
dependencies:
balanced-match "^1.0.0"
brace-expansion@^5.0.5:
version "5.0.7"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.7.tgz#1b0e46965b479dad65af737b4a02790a05498337"
integrity sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==
brace-expansion@^5.0.8:
version "5.0.8"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-5.0.8.tgz#135ad0d8d808eb18eb5e0ec9a21f3a0b92ef18cf"
integrity sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==
dependencies:
balanced-match "^4.0.2"
@ -3258,13 +3258,13 @@ braces@^3.0.3:
fill-range "^7.1.1"
browserslist@^4.24.0, browserslist@^4.28.1:
version "4.28.6"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.6.tgz#7cf83afcd69c55fde6fb2dcc5039ff0f4ba42610"
integrity sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==
version "4.28.7"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.28.7.tgz#409046517fccd2e51cdc20f077454b7141184028"
integrity sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==
dependencies:
baseline-browser-mapping "^2.10.42"
caniuse-lite "^1.0.30001803"
electron-to-chromium "^1.5.389"
baseline-browser-mapping "^2.10.44"
caniuse-lite "^1.0.30001806"
electron-to-chromium "^1.5.393"
node-releases "^2.0.51"
update-browserslist-db "^1.2.3"
@ -3334,7 +3334,7 @@ camelcase@^6.2.0:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
caniuse-lite@^1.0.30001803:
caniuse-lite@^1.0.30001806:
version "1.0.30001806"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz#1bc8e502b723fa393455dfbedd5ccec0c29bb74e"
integrity sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==
@ -3614,6 +3614,14 @@ css-select@^5.1.0:
domutils "^3.0.1"
nth-check "^2.0.1"
css-tree@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
dependencies:
mdn-data "2.0.14"
source-map "^0.6.1"
css-what@^6.1.0:
version "6.2.2"
resolved "https://registry.yarnpkg.com/css-what/-/css-what-6.2.2.tgz#cdcc8f9b6977719fdfbd1de7aec24abf756b9dea"
@ -3651,7 +3659,7 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
dayjs@^1.8.15:
dayjs@^1.11.21, dayjs@^1.8.15:
version "1.11.21"
resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.21.tgz#57f87562e62de76f3c704bd2b8d522fc33068eb2"
integrity sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==
@ -3813,10 +3821,10 @@ ee-first@1.1.1:
resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d"
integrity sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==
electron-to-chromium@^1.5.389:
version "1.5.392"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.392.tgz#31e9f9af0d8df3d1489468a4242b173605617482"
integrity sha512-1yQq3VQCZRwsnYc67Oc+1fge6Lwtn0hzi6zmEVkB61Zx21kTbwJAW4dFLadl5Rc1tKhG/kSpYXnfiAhu0f0a1g==
electron-to-chromium@^1.5.393:
version "1.5.398"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.398.tgz#9d4d979344ac762840293e3eacbebed34228aa16"
integrity sha512-AsvhAxopJGh6museTDMIjn6JpDYOfgu4RLlygomt87MUwBUqTfd/1EiPtx10/LZE8xpTvkP2E9Gafq7lkLtodQ==
emittery@^0.13.1:
version "0.13.1"
@ -4060,9 +4068,9 @@ eslint-plugin-ft-flow@^2.0.1:
string-natural-compare "^3.0.1"
eslint-plugin-jest@^29.0.1:
version "29.15.4"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.15.4.tgz#0c5371fc87499c6c5c25d60095340d05e396f58b"
integrity sha512-6ln5i9Nkrb27X4w91ZPt/xHDsVQnvxTS2ntgq6r32u+8gymdUrp88TdcBXSveZW0Dl+M5v2H6K75kJhMvUGhjg==
version "29.16.0"
resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-29.16.0.tgz#0810a9eb2753287f45485865942a22e740549d6a"
integrity sha512-0WFBxDHlT2ratGQfnFQEVIsgQJ5cfd+0IV8Kc6U3X2onB8ATLG23voD2Ch5G9fCkEpCPmCMuzW0tbS0kYb8biw==
dependencies:
"@typescript-eslint/utils" "^8.0.0"
@ -4447,9 +4455,9 @@ flat-cache@^3.0.4:
rimraf "^3.0.2"
flatted@^3.2.9:
version "3.4.2"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.2.tgz#f5c23c107f0f37de8dbdf24f13722b3b98d52726"
integrity sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==
version "3.4.4"
resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.4.4.tgz#aeeca2a506303f0cee61c59e6c9f2a88d2f29fc6"
integrity sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==
flow-enums-runtime@^0.0.6:
version "0.0.6"
@ -4468,7 +4476,7 @@ for-each@^0.3.3, for-each@^0.3.5:
dependencies:
is-callable "^1.2.7"
form-data@^4.0.5:
form-data@^4.0.6:
version "4.0.6"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.6.tgz#28e864e1b786dbebb68db1f452f9635278665827"
integrity sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==
@ -4591,6 +4599,11 @@ getenv@^2.0.0:
resolved "https://registry.yarnpkg.com/getenv/-/getenv-2.0.0.tgz#b1698c7b0f29588f4577d06c42c73a5b475c69e0"
integrity sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==
gifted-charts-core@0.1.81:
version "0.1.81"
resolved "https://registry.yarnpkg.com/gifted-charts-core/-/gifted-charts-core-0.1.81.tgz#6e8930d73f8026ca59f80a0edf4a24849016d034"
integrity sha512-plgJSbKB0Lxp2KQ/Fvj1qbOhiy6wxPiZ0Av60iFHpSSu6YlJjYhwczx5w2/iJQdZYb851OFMHgN/pgTNVLv6dA==
glob-parent@^5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@ -4844,9 +4857,9 @@ image-size@^1.0.2:
queue "6.0.2"
immer@^11.0.0:
version "11.1.11"
resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.11.tgz#bbf825a333ae1b16fd450d8da5f61d54de6a553d"
integrity sha512-qzXuyXAkPySAGYkfsAwodDPWT8Zm7/Uo5BNt4BjhMhG5WlWyZZ4wQqnWwdS8kjlQ1Cwu6gjw3A6+0gTQwlyYtw==
version "11.1.15"
resolved "https://registry.yarnpkg.com/immer/-/immer-11.1.15.tgz#1b178a9338486ade5939fa76cff43f4a49001eb0"
integrity sha512-VrNANlmnWQnh5COXIIOQXM9oOJw7naGKlBT74ZOOR6lpVXc3gFEu9FJLDFcpCJ2j+NWr8TIwtWD//T6ZX6TKiQ==
import-fresh@^3.2.1, import-fresh@^3.3.0:
version "3.3.1"
@ -5829,6 +5842,11 @@ math-intrinsics@^1.1.0:
resolved "https://registry.yarnpkg.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
integrity sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==
mdn-data@2.0.14:
version "2.0.14"
resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.14.tgz#7113fc4281917d63ce29b43446f701e68c25ba50"
integrity sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==
media-typer@0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748"
@ -6089,11 +6107,11 @@ mimic-fn@^2.1.0:
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
minimatch@^10.2.2:
version "10.2.5"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.5.tgz#bd48687a0be38ed2961399105600f832095861d1"
integrity sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==
version "10.2.6"
resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-10.2.6.tgz#fd956bbe0b77241e9f15ac5dccb1c638060968ef"
integrity sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==
dependencies:
brace-expansion "^5.0.5"
brace-expansion "^5.0.8"
minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
version "3.1.5"
@ -6198,9 +6216,9 @@ node-releases@^2.0.51:
integrity sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==
node-stream-zip@^1.9.1:
version "1.15.0"
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea"
integrity sha512-LN4fydt9TqhZhThkZIVQnF9cwjU3qmUH9h78Mx/K7d3VvfRqqwthLwJEUOEL0QPZ0XQmNN7be5Ggit5+4dq3Bw==
version "1.16.0"
resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.16.0.tgz#37d8e5d34e48cff6c2eb7ac68702ceec7aa3a7b8"
integrity sha512-ObaRrRoR8T68wF6suxHd7R4XQNamij6ZQHrwG7Dx1D2zeHcDNLsIOBcWrIwtDm7AsCXBguaPHgXhcjxDa2szrg==
normalize-path@^3.0.0:
version "3.0.0"
@ -6366,11 +6384,12 @@ ora@^5.4.1:
wcwidth "^1.0.1"
own-keys@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.1.tgz#e4006910a2bf913585289676eebd6f390cf51358"
integrity sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==
version "1.0.2"
resolved "https://registry.yarnpkg.com/own-keys/-/own-keys-1.0.2.tgz#31448ec1f781ecb1447f6f6aa0d6534222af59de"
integrity sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==
dependencies:
get-intrinsic "^1.2.6"
call-bound "^1.0.4"
get-intrinsic "^1.3.0"
object-keys "^1.1.1"
safe-push-apply "^1.0.0"
@ -6679,9 +6698,9 @@ react-is@^18.0.0:
integrity sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==
react-is@^19.1.0, react-is@^19.2.3:
version "19.2.7"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.7.tgz#57668ee86a78574a542b0a539455212b2c086df2"
integrity sha512-kZFnouyVv7eP/Phmrlo9FK+zcAdriZJvzxXHF1Sl1P377WSGe2G/JxVolhTrB/jeV47lKImhNUsijjHAAbcl/A==
version "19.2.8"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-19.2.8.tgz#09826f9fbc187bc668e3e5c62edc001f804d5018"
integrity sha512-s5un28nYxKJw5gvUHyW5PCC28CvBqLu9r3cWgzHT4Vo/5fqqkFcdRYsGcKf50WMPpjjFZS5d76fn3YCo2njKwQ==
react-native-bootsplash@^7.3.2:
version "7.3.2"
@ -6705,10 +6724,15 @@ react-native-config@^1.6.1:
resolved "https://registry.yarnpkg.com/react-native-config/-/react-native-config-1.6.1.tgz#f8428829fda74384484d773fe36820159c559159"
integrity sha512-HvKtxr6/Tq3iMdFx5REYZsjCtPi0RxQOMCs15+DqrUPTNFtWHuEuh+zw7fJp+dmuO79YMfdtlsPWIGTHtaXwjg==
react-native-drawer-layout@^4.2.7:
version "4.2.7"
resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.2.7.tgz#46a7a3ec3b0c05f6bea763751bc27a6ce9e5b650"
integrity sha512-hhD+E0QmUPkP2Sj1MsUdrvU7GeOiHChPAFPtKahroTwlBGnpgJsUVSL0GWOy5cG3oCZfnu4Pb+gIzOa4ItGNuA==
react-native-device-info@^15.0.2:
version "15.0.2"
resolved "https://registry.yarnpkg.com/react-native-device-info/-/react-native-device-info-15.0.2.tgz#c7de1bb6baeb07e1bab70159ad540c5705cb8dc0"
integrity sha512-dd71eXG2l3Cwp66IvKNadMTB8fhU3PEjyVddI97sYan+D4bgIAUmgGDhbSOFvHcGavksb2U17kiQYaDiK2WK2g==
react-native-drawer-layout@^4.2.9:
version "4.2.9"
resolved "https://registry.yarnpkg.com/react-native-drawer-layout/-/react-native-drawer-layout-4.2.9.tgz#848d36cdd5f4d92c95257ae8121ad998a567398b"
integrity sha512-ETOxvlhhb4LmuuG3RN7A3qwt9jr9AZ2it+1G2kNE4g2fTyxxay7QQkPm1HfKi/JzmMSWC5+YTepGSgZNpJIDGg==
dependencies:
color "^4.2.3"
use-latest-callback "^0.2.4"
@ -6723,15 +6747,27 @@ react-native-gesture-handler@^2.32.0:
hoist-non-react-statics "^3.3.0"
invariant "^2.2.4"
react-native-gifted-charts@^1.4.77:
version "1.4.77"
resolved "https://registry.yarnpkg.com/react-native-gifted-charts/-/react-native-gifted-charts-1.4.77.tgz#8e1195b3144f221d675729b636819e13e7e0e071"
integrity sha512-Ul4juHO0Gicng139i61AzQ3h4kLM25dzid1rU+d3d7PuHsI4UypgeChz/luaHMZaWgXkALjq6DLqaM2Y2AEhrA==
dependencies:
gifted-charts-core "0.1.81"
react-native-is-edge-to-edge@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz#feb9a6a8faf0874298947edd556e5af22044e139"
integrity sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==
react-native-linear-gradient@^2.8.3:
version "2.8.3"
resolved "https://registry.yarnpkg.com/react-native-linear-gradient/-/react-native-linear-gradient-2.8.3.tgz#9a116649f86d74747304ee13db325e20b21e564f"
integrity sha512-KflAXZcEg54PXkLyflaSZQ3PJp4uC4whM7nT/Uot9m0e/qxFV3p6uor1983D1YOBJbJN7rrWdqIjq0T42jOJyA==
react-native-reanimated@^4.5.0:
version "4.5.1"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-4.5.1.tgz#be81ed3a96bf70baeeccdc24d3350883099b7dd3"
integrity sha512-RnMvtDuR+68ig864gAvZCOdZehqhC5rFmMo0kn+ARfgVSTvFeF6IFLBVgMPUu0KwihaapEyW24WRi6nEyy1kSA==
version "4.5.3"
resolved "https://registry.yarnpkg.com/react-native-reanimated/-/react-native-reanimated-4.5.3.tgz#10ec3f1b2c8d710033d707199cab591a0d410d70"
integrity sha512-+owIckpD4sA13XKHaLrr8V1RP4paqoxKvz/jy2wl/ULjMsfcRlfIJdKJxR1kcQr4McBZ6hAoPo01NyLnmy2ycQ==
dependencies:
react-native-is-edge-to-edge "^1.3.1"
semver "^7.7.3"
@ -6742,13 +6778,21 @@ react-native-safe-area-context@^5.8.0:
integrity sha512-t+ZsAVzY/wWzzx34vqGbo3/as9EEESJdbyZNL7Yg5EYX+toYMtMqFoDDCvqZUi35eeGVsXc6pAaEk4edMwbuCQ==
react-native-screens@^4.25.2:
version "4.26.1"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.26.1.tgz#98a580bcb244f93e931391f4af43d405b557efba"
integrity sha512-4aaCRSm1CXqjTiph37uFytz5NUxIeiOdYTnetkRoUbEIQhqUa4ShsjQUuKC42Pe+JEBw13YkM05TJXMdlrPy/A==
version "4.26.2"
resolved "https://registry.yarnpkg.com/react-native-screens/-/react-native-screens-4.26.2.tgz#cfc007736526a0958e8e8d3ff5f77202c323a852"
integrity sha512-2XnWsZToKj76trGtEZzx5ELD/qOICFEprEeUntImmitQFVUkea27fiWdUSITArI356Y1qynpXZINW+Unbhky/A==
dependencies:
react-freeze "^1.0.0"
warn-once "^0.1.0"
react-native-svg@^15.15.5:
version "15.15.5"
resolved "https://registry.yarnpkg.com/react-native-svg/-/react-native-svg-15.15.5.tgz#822805c14481b8ec16d5fbac1e3ce2b27a5509d7"
integrity sha512-L4go5jA+GWutdJ/JucuN20cjAbMg1HmMtAP+wZ+3JLCf6Jd0bhXQHxciRP/AQm/FlrIEZwkMcHNZP+FXAiic0w==
dependencies:
css-select "^5.1.0"
css-tree "^1.1.3"
react-native-vector-icons@^10.3.0:
version "10.3.0"
resolved "https://registry.yarnpkg.com/react-native-vector-icons/-/react-native-vector-icons-10.3.0.tgz#de440f2627a2ed1079ce3b99d5b9d4f86894df28"
@ -6758,9 +6802,9 @@ react-native-vector-icons@^10.3.0:
yargs "^16.1.1"
react-native-worklets@^0.10.2:
version "0.10.2"
resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.10.2.tgz#c59e6314d2da4a69f20d66f58bb964ee2275328a"
integrity sha512-LX27ejYI8veeDp59Z3rjo2pYyPa9euzSH8GUlem7cnNqfsDtGum8PQpkbzrqhLsWH0CjdeHR7p3sncCyYbwaVw==
version "0.10.3"
resolved "https://registry.yarnpkg.com/react-native-worklets/-/react-native-worklets-0.10.3.tgz#c4873813215207ba78cf2db9c4ff411e7be2c8cc"
integrity sha512-NaDBNXc2l6p8ggNYJ2iMfD1Yov/PI1CTwL+d0Lczih18C0Tw37mY0sJd9DXi1zZNBeltAsWyZSSr1nOEdren3w==
dependencies:
"@babel/plugin-transform-arrow-functions" "^7.27.1"
"@babel/plugin-transform-class-properties" "^7.28.6"
@ -7081,9 +7125,9 @@ safe-regex-test@^1.1.0:
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sax@>=0.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.0.tgz#da59637629307b97e7c4cb28e080a7bc38560d5b"
integrity sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==
version "1.6.1"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.6.1.tgz#4c23cf608c0b693ab54b4b5888e92cfe977b9843"
integrity sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==
scheduler@0.27.0, scheduler@^0.27.0:
version "0.27.0"
@ -7372,10 +7416,10 @@ stacktrace-parser@^0.1.10:
dependencies:
type-fest "^0.7.1"
standard-navigation@^0.0.7:
version "0.0.7"
resolved "https://registry.yarnpkg.com/standard-navigation/-/standard-navigation-0.0.7.tgz#8c5265c4244446e0fb2b4bc9861a9979dd34f22f"
integrity sha512-NCGLCNyuXrFOkGHxdNZFnpsehGtiq1oXbPhKl7ZuxFO5J//H2evqqOchmD4YwEUJnkjO4kH9Xp4hQX6hdAYCKQ==
standard-navigation@^0.0.8:
version "0.0.8"
resolved "https://registry.yarnpkg.com/standard-navigation/-/standard-navigation-0.0.8.tgz#aef79bef130ec8ba0a9c070d3a728d96d68df8a8"
integrity sha512-TyVbo7INUDWtsUWDFn8RR7kwR87U0S4xHfLfbbnyeC581TmmyqQ+eM+nPw8rQTSD8QitRVcYfPaSHr/QJiUy1g==
statuses@~1.5.0:
version "1.5.0"
@ -7953,16 +7997,16 @@ write-file-atomic@^4.0.2:
signal-exit "^3.0.7"
ws@^6.2.3:
version "6.2.5"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.5.tgz#62a472481a10629989009e720af28431e7c2ba21"
integrity sha512-T7pPl+DnmNrKRuttAIQwueReX5GqsedYEWp3/H//CG35+DyUOe1+/voAeE8idxgfLsQf2tO+rdtBd1FnPopgTQ==
version "6.2.6"
resolved "https://registry.yarnpkg.com/ws/-/ws-6.2.6.tgz#545a6a915b00b660937f7f2c3084d623fb2fc59f"
integrity sha512-XTrf1gv7kXoVf1hbC3PAyAiPgR8Wz1blcrYIjEsUmr08BLksT41R8KbjmS9408C2ERx7v1JDLD/BkpLEttjfKA==
dependencies:
async-limiter "~1.0.0"
ws@^7, ws@^7.5.10:
version "7.5.12"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.12.tgz#4ca2c04966db4dcd2f9bc4d1419d23cd1e4a18db"
integrity sha512-1xGnbYN3zbog9CwuNDQULNRrTCLIn46/WmpR1f0w6PsCYQHkylZr5vkd6kfMZYV6pRnQkcPNRyiA8LsrNKyhpg==
version "7.5.13"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.13.tgz#12aa507eaca76c295c278b1aebf4698ab2c1845f"
integrity sha512-rsKI6xDBFVf4r/x8XyChGK04QR/XHroxs/jUcoWvtEZM8TPU/X/uIY9B1CsSzYws9ZJb/6bbBu7dPhFW00CAoA==
xcode@^3.0.1:
version "3.0.1"