diff --git a/.env.example b/.env.example
new file mode 100644
index 0000000..01bd409
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,20 @@
+# Copy this file to `.env` and fill in your values
+# These are consumed by app.config.js via dotenv at build time
+
+# Firebase config (also present in google-services.json / GoogleService-Info.plist for native)
+FIREBASE_API_KEY=
+FIREBASE_AUTH_DOMAIN=
+FIREBASE_PROJECT_ID=
+FIREBASE_STORAGE_BUCKET=
+FIREBASE_MESSAGING_SENDER_ID=
+FIREBASE_APP_ID=
+
+# Google Sign-In: REQUIRED for offline access and proper sign-in on native
+# Provide the OAuth 2.0 Client ID of type "Web application" from Google Cloud console (same project as your iOS/Android clients)
+GOOGLE_WEB_CLIENT_ID=
+
+# Optional: Maps (Android)
+GOOGLE_MAPS_API_KEY=
+
+# Build metadata
+BUILD_DATE=
diff --git a/.github/workflows/eas-android-build.yml b/.github/workflows/eas-android-build.yml
index f52813d..84c851b 100644
--- a/.github/workflows/eas-android-build.yml
+++ b/.github/workflows/eas-android-build.yml
@@ -4,7 +4,7 @@ permissions:
on:
push:
- branches: [main]
+ branches: ["**"] # Run on all branches
workflow_dispatch:
env:
@@ -21,6 +21,7 @@ jobs:
build_number: ${{ steps.version-control.outputs.build_number }}
build_date: ${{ steps.version-control.outputs.build_date }}
is_production: ${{ steps.version-control.outputs.is_production }}
+ branch_name: ${{ steps.extract-branch.outputs.branch_name }}
steps:
# ========================
# π οΈ Repository Setup
@@ -30,6 +31,14 @@ jobs:
with:
fetch-depth: 0
+ - name: "π Extract branch name"
+ id: extract-branch
+ shell: bash
+ run: |
+ BRANCH_NAME=${GITHUB_REF#refs/heads/}
+ echo "branch_name=$BRANCH_NAME" >> $GITHUB_OUTPUT
+ echo "Branch: $BRANCH_NAME"
+
# ========================
# βοΈ Environment Configuration
# ========================
@@ -73,7 +82,12 @@ jobs:
APP_VERSION=$(jq -r '.version' version.json)
IS_PRODUCTION="true"
else
- APP_VERSION="1.0.0-prerelease.${{ github.run_number }}"
+ # For non-main branches, create a prerelease version with branch name
+ BRANCH_NAME=${{ steps.extract-branch.outputs.branch_name }}
+ SANITIZED_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9]/-/g')
+ # Get base version from version.json
+ BASE_VERSION=$(jq -r '.version' version.json)
+ APP_VERSION="${BASE_VERSION}-pre.${SANITIZED_BRANCH}.${{ github.run_number }}"
IS_PRODUCTION="false"
fi
@@ -109,7 +123,17 @@ jobs:
run: |
echo "π Initializing build process..."
sudo apt-get install -y jq
- BUILD_JSON=$(npx eas build -p android --profile production --non-interactive --json)
+
+ # Choose profile based on branch
+ if [ "${{ github.ref }}" == "refs/heads/main" ]; then
+ BUILD_PROFILE="production"
+ else
+ BUILD_PROFILE="preview" # Use a different profile for pre-releases if needed
+ fi
+
+ echo "Using build profile: $BUILD_PROFILE"
+
+ BUILD_JSON=$(npx eas build -p android --profile $BUILD_PROFILE --non-interactive --json)
echo "Raw build output: $BUILD_JSON"
BUILD_ID=$(echo "$BUILD_JSON" | jq -r '.[0].id')
if [[ -z "$BUILD_ID" || "$BUILD_ID" == "null" ]]; then
@@ -274,7 +298,6 @@ jobs:
CHANGELOG=$(git log --pretty=format:"- %s (%h) by %an" -n 15)
echo "$CHANGELOG" > changelog.txt
- # FIXED OUTPUT HANDLING (ONLY CHANGE)
delimiter=$(openssl rand -hex 6)
echo "CHANGELOG<<${delimiter}" >> $GITHUB_OUTPUT
cat changelog.txt >> $GITHUB_OUTPUT
@@ -316,11 +339,14 @@ jobs:
RELEASE_TAG="v${{ needs.build-android.outputs.app_version }}"
RELEASE_TITLE="Production Release v${{ needs.build-android.outputs.app_version }}"
else
- echo "π‘ Nightly build detected"
- RELEASE_TAG="nightly-${{ needs.build-android.outputs.build_date }}"
- RELEASE_TITLE="Nightly Build (${{ needs.build-android.outputs.build_date }})"
+ echo "π‘ Pre-release build detected"
+ BRANCH_NAME="${{ needs.build-android.outputs.branch_name }}"
+ RELEASE_TAG="prerelease-${BRANCH_NAME}-${{ needs.build-android.outputs.build_date }}"
+ RELEASE_TITLE="Pre-release (${BRANCH_NAME}) v${{ needs.build-android.outputs.app_version }}"
fi
echo "RELEASE_TAG=${RELEASE_TAG}" >> $GITHUB_OUTPUT
+ echo "RELEASE_TITLE=${RELEASE_TITLE}" >> $GITHUB_OUTPUT
+
- name: "π Publish GitHub Release"
uses: softprops/action-gh-release@v2
with:
diff --git a/GoogleService-Info.plist b/GoogleService-Info.plist
index b014b2e..7fb687e 100644
--- a/GoogleService-Info.plist
+++ b/GoogleService-Info.plist
@@ -24,6 +24,10 @@
IS_SIGNIN_ENABLED
+ CLIENT_ID
+ 906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com
+ REVERSED_CLIENT_ID
+ com.googleusercontent.apps.906784991953-aoq1tgiac6ge9aqv4kguai26265393h9
GOOGLE_APP_ID
1:982053776531:ios:eec4ee1692d95d2ce3b166
diff --git a/README.md b/README.md
index 67da738..04669ae 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,7 @@ CodeBuilder Admin is a mobile application built with React Native and Expo. This
```env
GOOGLE_MAPS_API_KEY=your_google_maps_api_key
+ GOOGLE_WEB_CLIENT_ID=your_google_web_client_id
FIREBASE_API_KEY=your_firebase_api_key
FIREBASE_AUTH_DOMAIN=your_firebase_auth_domain
FIREBASE_PROJECT_ID=your_firebase_project_id
diff --git a/app.config.js b/app.config.js
index 97e869a..736affe 100644
--- a/app.config.js
+++ b/app.config.js
@@ -1,152 +1,153 @@
-import dotenv from "dotenv";
-import withNotificationToolsReplace from "./plugins/test.cjs";
-import versionData from "./version.json";
-import { withPlugins } from "@expo/config-plugins";
-import { withXcodeProject } from "expo/config-plugins";
-import fs from "fs";
+import dotenv from 'dotenv';
+import withNotificationToolsReplace from './plugins/test.cjs';
+import versionData from './version.json';
+import { withPlugins } from '@expo/config-plugins';
+import { withXcodeProject } from 'expo/config-plugins';
+import fs from 'fs';
// Explicitly load the .env file
dotenv.config();
// Add this new plugin
const withIOSSounds = (config) => {
- return withXcodeProject(config, async (cfg) => {
- const xcodeProject = cfg.modResults;
- const appName = "CodeBuilder Admin"; // Match your iOS project name
- const soundFiles = fs.readdirSync(`./ios/${appName}/Sounds`);
+ return withXcodeProject(config, async (cfg) => {
+ const xcodeProject = cfg.modResults;
+ const appName = 'CodeBuilder Admin'; // Match your iOS project name
+ const soundFiles = fs.readdirSync(`./ios/${appName}/Sounds`);
- soundFiles.forEach((file) => {
- xcodeProject.addResourceFile({
- path: `${appName}/Sounds/${file}`,
- group: "Resources",
- });
- });
+ soundFiles.forEach((file) => {
+ xcodeProject.addResourceFile({
+ path: `${appName}/Sounds/${file}`,
+ group: 'Resources',
+ });
+ });
- return cfg;
- });
+ return cfg;
+ });
};
module.exports = {
- expo: {
- name: "CodeBuilder Admin",
- slug: "codebuilder",
- version: versionData.version, // Using version from version.json
- extra: {
- buildDate: process.env.BUILD_DATE || new Date().toISOString(),
- eas: {
- projectId: "c382aeb5-b138-47fb-83b4-dc45ab02ce76",
- },
- firebaseApiKey: process.env.FIREBASE_API_KEY,
- firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
- firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
- firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
- firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
- firebaseAppId: process.env.FIREBASE_APP_ID,
- },
- orientation: "portrait",
- icon: "./assets/images/icon.png",
- scheme: "codebuilder-admin",
- userInterfaceStyle: "automatic",
- newArchEnabled: true,
- notification: {
- icon: "./assets/images/icon.png",
- color: "#ffffff", // Optional: Tint color for the icon
- },
- splash: {
- image: "./assets/images/splash-icon.png",
- resizeMode: "contain",
- backgroundColor: "#ffffff",
- },
- ios: {
- buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json
- supportsTablet: true,
- bundleIdentifier: "com.digitalnomad91.codebuilderadmin",
- googleServicesFile: "./GoogleService-Info.plist",
- entitlements: {
- "aps-environment": "production",
- },
- config: {
- // Provide your Google Maps API key
- googleMapsApiKey: "YOUR_IOS_GOOGLE_MAPS_API_KEY",
- },
- },
- android: {
- versionCode: versionData.androidVersionCode, // Using Android versionCode from version.json
- adaptiveIcon: {
- foregroundImage: "./assets/images/adaptive-icon.png",
- backgroundColor: "#ffffff",
- },
- package: "com.digitalnomad91.codebuilderadmin",
- permissions: ["NOTIFICATIONS", "POST_NOTIFICATIONS", "READ_PHONE_STATE"],
- googleServicesFile: "./google-services.json",
- useNextNotificationsApi: true,
- notification: {
- icon: "./assets/images/icon.png",
- color: "#ffffff", // Optional: Tint color for the icon
- },
- config: {
- googleMaps: {
- apiKey: process.env.GOOGLE_MAPS_API_KEY,
+ expo: {
+ name: 'CodeBuilder Admin',
+ slug: 'codebuilder',
+ version: versionData.version, // Using version from version.json
+ extra: {
+ buildDate: process.env.BUILD_DATE || new Date().toISOString(),
+ eas: {
+ projectId: 'c382aeb5-b138-47fb-83b4-dc45ab02ce76',
+ },
+ firebaseApiKey: process.env.FIREBASE_API_KEY,
+ firebaseAuthDomain: process.env.FIREBASE_AUTH_DOMAIN,
+ firebaseProjectId: process.env.FIREBASE_PROJECT_ID,
+ firebaseStorageBucket: process.env.FIREBASE_STORAGE_BUCKET,
+ firebaseMessagingSenderId: process.env.FIREBASE_MESSAGING_SENDER_ID,
+ firebaseAppId: process.env.FIREBASE_APP_ID,
+ googleWebClientId: process.env.GOOGLE_WEB_CLIENT_ID || process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID,
},
- },
- // manifest: {
- // application: {
- // metaData: [
- // {
- // "android:name": "com.google.firebase.messaging.default_notification_color",
- // "android:resource": "@color/notification_icon_color",
- // "tools:replace": "android:resource",
- // },
- // ],
- // },
- // },
- },
- web: {
- bundler: "metro",
- output: "web-build",
- favicon: "./assets/images/favicon.png",
- },
- plugins: [
- withNotificationToolsReplace,
- [
- "expo-camera",
- {
- cameraPermission: "Allow $(PRODUCT_NAME) to access your camera",
- microphonePermission:
- "Allow $(PRODUCT_NAME) to access your microphone",
- recordAudioAndroid: true,
+ orientation: 'portrait',
+ icon: './assets/images/icon.png',
+ scheme: 'codebuilder-admin',
+ userInterfaceStyle: 'automatic',
+ newArchEnabled: true,
+ notification: {
+ icon: './assets/images/icon.png',
+ color: '#ffffff', // Optional: Tint color for the icon
+ },
+ splash: {
+ image: './assets/images/splash-icon.png',
+ resizeMode: 'contain',
+ backgroundColor: '#ffffff',
+ },
+ ios: {
+ buildNumber: versionData.iosBuildNumber, // Using iOS build number from version.json
+ supportsTablet: true,
+ bundleIdentifier: 'com.digitalnomad91.codebuilderadmin',
+ googleServicesFile: './GoogleService-Info.plist',
+ entitlements: {
+ 'aps-environment': 'production',
+ },
+ config: {
+ // Provide your Google Maps API key
+ googleMapsApiKey: 'YOUR_IOS_GOOGLE_MAPS_API_KEY',
+ },
+ },
+ android: {
+ versionCode: versionData.androidVersionCode, // Using Android versionCode from version.json
+ adaptiveIcon: {
+ foregroundImage: './assets/images/adaptive-icon.png',
+ backgroundColor: '#ffffff',
+ },
+ package: 'com.digitalnomad91.codebuilderadmin',
+ permissions: ['NOTIFICATIONS', 'POST_NOTIFICATIONS', 'READ_PHONE_STATE'],
+ googleServicesFile: './google-services.json',
+ useNextNotificationsApi: true,
+ notification: {
+ icon: './assets/images/icon.png',
+ color: '#ffffff', // Optional: Tint color for the icon
+ },
+ config: {
+ googleMaps: {
+ apiKey: process.env.GOOGLE_MAPS_API_KEY,
+ },
+ },
+ // manifest: {
+ // application: {
+ // metaData: [
+ // {
+ // "android:name": "com.google.firebase.messaging.default_notification_color",
+ // "android:resource": "@color/notification_icon_color",
+ // "tools:replace": "android:resource",
+ // },
+ // ],
+ // },
+ // },
},
- ],
- "expo-router",
- [
- "expo-build-properties",
- {
- newArchEnabled: false,
- ios: {
- flipper: false,
- useFrameworks: "static",
- },
- android: {
- compileSdkVersion: 35,
- targetSdkVersion: 35,
- buildToolsVersion: "35.0.0",
- },
+ web: {
+ bundler: 'metro',
+ output: 'web-build',
+ favicon: './assets/images/favicon.png',
},
- ],
- "@react-native-firebase/app",
- "@react-native-firebase/messaging",
- // Add the iOS sound plugin
- [withIOSSounds],
- [
- "expo-notifications",
- {
- sounds: ["./assets/sounds/notification.aiff"],
+ plugins: [
+ withNotificationToolsReplace,
+ [
+ 'expo-camera',
+ {
+ cameraPermission: 'Allow $(PRODUCT_NAME) to access your camera',
+ microphonePermission: 'Allow $(PRODUCT_NAME) to access your microphone',
+ recordAudioAndroid: true,
+ },
+ ],
+ 'expo-router',
+ [
+ 'expo-build-properties',
+ {
+ newArchEnabled: false,
+ ios: {
+ flipper: false,
+ useFrameworks: 'static',
+ },
+ android: {
+ compileSdkVersion: 35,
+ targetSdkVersion: 35,
+ buildToolsVersion: '35.0.0',
+ },
+ },
+ ],
+ '@react-native-firebase/app',
+ '@react-native-firebase/messaging',
+ '@react-native-google-signin/google-signin',
+ // Add the iOS sound plugin
+ [withIOSSounds],
+ [
+ 'expo-notifications',
+ {
+ sounds: ['./assets/sounds/notification.aiff'],
+ },
+ ],
+ ],
+ experiments: {
+ typedRoutes: true,
+ reactCanary: true,
},
- ],
- ],
- experiments: {
- typedRoutes: true,
- reactCanary: true,
},
- },
};
diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx
index 9c88bd2..677eb4e 100644
--- a/app/(tabs)/_layout.tsx
+++ b/app/(tabs)/_layout.tsx
@@ -1,74 +1,64 @@
-import FontAwesome from "@expo/vector-icons/FontAwesome";
-import { Link, Tabs } from "expo-router";
-import { Pressable, Image, StyleSheet } from "react-native";
+import FontAwesome from '@expo/vector-icons/FontAwesome';
+import { Link, Tabs } from 'expo-router';
+import { Pressable, Image, StyleSheet } from 'react-native';
-import Colors from "@/constants/Colors";
-import { useColorScheme } from "@/hooks/useColorScheme";
-import { useClientOnlyValue } from "@/hooks/useClientOnlyValue";
+import Colors from '@/constants/Colors';
+import { useColorScheme } from '@/hooks/useColorScheme';
+import { useClientOnlyValue } from '@/hooks/useClientOnlyValue';
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
-function TabBarIcon(props: {
- name: React.ComponentProps["name"];
- color: string;
-}) {
- return ;
+function TabBarIcon(props: { name: React.ComponentProps['name']; color: string }) {
+ return ;
}
export default function TabLayout() {
- const colorScheme = useColorScheme();
- return (
-
- ,
- headerRight: () => (
-
-
- {({ pressed }) => (
-
- )}
-
-
- ),
- }}
- />
- (
-
- ),
- }}
- />
- ,
- }}
- />
- ,
- }}
- />
-
- );
+ const colorScheme = useColorScheme();
+ return (
+
+ ,
+ headerRight: () => (
+
+
+ {({ pressed }) => (
+
+ )}
+
+
+ ),
+ }}
+ />
+ ,
+ }}
+ />
+ ,
+ }}
+ />
+ ,
+ }}
+ />
+
+ );
}
diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx
index 25e4a13..7e9b748 100644
--- a/app/(tabs)/index.tsx
+++ b/app/(tabs)/index.tsx
@@ -1,142 +1,135 @@
-import {
- View,
- Text,
- Button,
- ActivityIndicator,
- StyleSheet,
- Image,
- ScrollView,
-} from "react-native";
-import MapView, { Marker } from "react-native-maps";
-import { useLocation } from "@/hooks/useLocation";
-import LogViewer from "@/components/LogViewer";
-import BatteryInfo from "@/components/BatteryInfo";
-import { triggerLocalSampleNotification } from "@/utils/notifications.utils";
-import { usePushNotifications } from "@/hooks/usePushNotifications";
+import { View, Text, Button, ActivityIndicator, StyleSheet, Image, ScrollView } from 'react-native';
+import MapView, { Marker } from 'react-native-maps';
+import { Link } from 'expo-router';
+import { useLocation } from '@/hooks/useLocation';
+import LogViewer from '@/components/LogViewer';
+import BatteryInfo from '@/components/BatteryInfo';
+import { triggerLocalSampleNotification } from '@/utils/notifications.utils';
+import { usePushNotifications } from '@/hooks/usePushNotifications';
export default function LocationComponent() {
- const { fcmToken } = usePushNotifications();
- const { location, address, error, loading, fetchLocation } =
- useLocation(fcmToken);
+ const { fcmToken } = usePushNotifications();
+ const { location, address, error, loading, fetchLocation } = useLocation(fcmToken);
- const textColor = "#ffffff";
+ const textColor = '#ffffff';
- return (
-
-
-
+ return (
+
+
+
-
+
- {/* Battery information */}
-
+ {/* Battery information */}
+
- {/* Location / Map */}
- {loading ? (
-
- ) : error ? (
- {error}
- ) : location && address ? (
- <>
-
- Address: {address.name}, {address.city}, {address.region},{" "}
- {address.country}
-
-
- {location.coords.latitude} - {location.coords.longitude}
-
+ {/* Location / Map */}
+ {loading ? (
+
+ ) : error ? (
+ {error}
+ ) : location && address ? (
+ <>
+
+ Address: {address.name}, {address.city}, {address.region}, {address.country}
+
+
+ {location.coords.latitude} - {location.coords.longitude}
+
-
-
- {location && (
-
+
+
+ {location && (
+
+ )}
+
+
+ >
+ ) : (
+ Waiting for location...
)}
-
-
- >
- ) : (
-
- Waiting for location...
-
- )}
-
-
-
-
- );
+
+
+
+ {/* Link to Login page */}
+
+
+
+
+
+
+
+ );
}
const styles = StyleSheet.create({
- scrollContent: {
- paddingVertical: 20,
- alignItems: "center",
- },
- container: {
- width: "90%",
- alignItems: "center",
- },
- text: {
- fontSize: 16,
- marginVertical: 8,
- textAlign: "center",
- },
- batteryContainer: {
- borderRadius: 8,
- padding: 10,
- marginVertical: 12,
- width: "100%",
- },
- batteryText: {
- color: "#FFFFFF",
- fontSize: 16,
- marginBottom: 4,
- },
- mapContainer: {
- width: "100%",
- height: 300,
- marginTop: 16,
- borderRadius: 10,
- overflow: "hidden",
- },
- map: {
- width: "100%",
- height: "100%",
- },
+ scrollContent: {
+ paddingVertical: 20,
+ alignItems: 'center',
+ },
+ container: {
+ width: '90%',
+ alignItems: 'center',
+ },
+ text: {
+ fontSize: 16,
+ marginVertical: 8,
+ textAlign: 'center',
+ },
+ batteryContainer: {
+ borderRadius: 8,
+ padding: 10,
+ marginVertical: 12,
+ width: '100%',
+ },
+ batteryText: {
+ color: '#FFFFFF',
+ fontSize: 16,
+ marginBottom: 4,
+ },
+ mapContainer: {
+ width: '100%',
+ height: 300,
+ marginTop: 16,
+ borderRadius: 10,
+ overflow: 'hidden',
+ },
+ map: {
+ width: '100%',
+ height: '100%',
+ },
});
const imgStyles = StyleSheet.create({
- image: {
- width: 50,
- height: 50,
- resizeMode: "contain",
- },
+ image: {
+ width: 50,
+ height: 50,
+ resizeMode: 'contain',
+ },
});
diff --git a/app/_layout.tsx b/app/_layout.tsx
index 64d061e..04c6fe8 100644
--- a/app/_layout.tsx
+++ b/app/_layout.tsx
@@ -1,119 +1,124 @@
-import { useEffect, useState } from "react";
-import { useNavigationContainerRef } from "@react-navigation/native";
-import { useReactNavigationDevTools } from "@dev-plugins/react-navigation";
-import { SplashScreen } from "expo-router";
-import {
- usePushNotifications,
- useNotificationObserver,
-} from "@/hooks/usePushNotifications";
-import { useFonts } from "expo-font";
-import FontAwesome from "@expo/vector-icons/FontAwesome";
-import {
- DarkTheme,
- DefaultTheme,
- ThemeProvider,
-} from "@react-navigation/native";
-import { Stack } from "expo-router";
-import "react-native-reanimated";
-import { useColorScheme } from "@/hooks/useColorScheme";
-import { registerBackgroundFetch } from "@/utils/tasks.utils";
-import ErrorBoundary from "@/components/ErrorBoundary";
-
-import {
- setJSExceptionHandler,
- setNativeExceptionHandler,
-} from "react-native-exception-handler";
-import { reportError, safeReport } from "@/services/errorReporting.service";
-import { showFatalErrorNotification } from "@/utils/notifications.utils";
+// app/_layout.tsx
+import { useEffect } from 'react';
+import { useNavigationContainerRef } from '@react-navigation/native';
+import { useReactNavigationDevTools } from '@dev-plugins/react-navigation';
+import { SplashScreen, Stack } from 'expo-router';
+import 'react-native-reanimated';
+
+import { useFonts } from 'expo-font';
+import FontAwesome from '@expo/vector-icons/FontAwesome';
+import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
+
+import { usePushNotifications, useNotificationObserver } from '@/hooks/usePushNotifications';
+import { useColorScheme } from '@/hooks/useColorScheme';
+import { registerBackgroundFetch } from '@/utils/tasks.utils';
+import ErrorBoundary from '@/components/ErrorBoundary';
+import { NotificationProvider } from '@/providers/NotificationProvider';
+
+// β
import from providers + hooks (donβt import AuthProvider from hooks)
+import { AuthProvider } from '@/providers/AuthProvider';
+import { useAuth } from '@/hooks/useAuth';
+
+import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
+import { safeReport } from '@/services/errorReporting.service';
+import { showFatalErrorNotification } from '@/utils/notifications.utils';
+// β
keep pathing consistent with your alias
+import { setupGlobalErrorHandlers } from '@/utils/globalErrorhandler';
+
+// Prevent auto-hiding the splash until fonts are loaded
+SplashScreen.preventAutoHideAsync().catch(() => {
+ /* no-op */
+});
// --- Global Exception Handler Setup ---
-
const jsExceptionHandler = (maybeError: unknown, isFatal: boolean) => {
- const error =
- maybeError instanceof Error
- ? maybeError
- : new Error(
- typeof maybeError === "string"
- ? maybeError
- : JSON.stringify(maybeError) || "Unknown non-Error thrown"
- );
-
- console.log("Caught JS Exception:", error, isFatal);
-
- // Use safeReport to avoid throwing from inside handler
- safeReport(error, {
- isFatal,
- errorInfo: { componentStack: "jsExceptionHandler" },
- });
-
- if (isFatal) {
- showFatalErrorNotification(error);
- }
+ const error = maybeError instanceof Error ? maybeError : new Error(typeof maybeError === 'string' ? maybeError : JSON.stringify(maybeError) || 'Unknown non-Error thrown');
+
+ console.log('Caught JS Exception:', error, isFatal);
+
+ safeReport(error, {
+ isFatal,
+ errorInfo: { componentStack: 'jsExceptionHandler' },
+ });
+
+ if (isFatal) showFatalErrorNotification(error);
};
const nativeExceptionHandler = (exceptionString: string) => {
- console.log("Caught Native Exception:", exceptionString);
- const error = new Error(`Native Exception: ${exceptionString}`);
+ console.log('Caught Native Exception:', exceptionString);
+ const error = new Error(`Native Exception: ${exceptionString}`);
- safeReport(error, {
- isFatal: true,
- errorInfo: { componentStack: "nativeExceptionHandler" },
- });
+ safeReport(error, {
+ isFatal: true,
+ errorInfo: { componentStack: 'nativeExceptionHandler' },
+ });
- showFatalErrorNotification(error);
+ showFatalErrorNotification(error);
};
setJSExceptionHandler(jsExceptionHandler, true);
setNativeExceptionHandler(nativeExceptionHandler);
// --- Component Definition ---
-
export default function RootLayout() {
- const navigationRef = useNavigationContainerRef();
-
- // Enable push notifications and observer
- usePushNotifications();
- useNotificationObserver();
-
- // Register background fetch task
- useEffect(() => {
- registerBackgroundFetch();
- }, []);
-
- // Enable React Navigation DevTools
- useReactNavigationDevTools(navigationRef);
-
- // Load fonts
- const [loaded] = useFonts({
- SpaceMono: require("../assets/fonts/SpaceMono-Regular.ttf"),
- ...FontAwesome.font,
- });
-
- // Hide the splash screen once fonts are loaded
- useEffect(() => {
- if (loaded) {
- SplashScreen.hideAsync();
- }
- }, [loaded]);
-
- if (!loaded) {
- return null;
- }
-
- return ;
+ const navigationRef = useNavigationContainerRef();
+
+ // Enable push notifications and observer
+ usePushNotifications();
+ useNotificationObserver();
+
+ // Register background fetch task
+ useEffect(() => {
+ registerBackgroundFetch();
+ }, []);
+
+ // React Navigation DevTools
+ useReactNavigationDevTools(navigationRef);
+
+ // Load fonts
+ const [loaded] = useFonts({
+ SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'),
+ ...FontAwesome.font,
+ });
+
+ // Hide the splash screen once fonts are loaded
+ useEffect(() => {
+ if (loaded) {
+ SplashScreen.hideAsync().catch(() => {});
+ }
+ }, [loaded]);
+
+ if (!loaded) return null;
+
+ return (
+
+
+
+ );
}
function RootLayoutNav() {
- const colorScheme = useColorScheme();
-
- return (
-
-
-
-
-
-
-
-
- );
+ const colorScheme = useColorScheme();
+ const { user } = useAuth();
+ // Avoid logging during render to prevent side-effects in LogViewer
+ useEffect(() => {
+ console.log('User: ', user);
+ }, [user]);
+
+ useEffect(() => {
+ setupGlobalErrorHandlers();
+ }, []);
+
+ return (
+
+
+
+
+ {user ? : }
+
+
+
+
+
+ );
}
diff --git a/app/login.tsx b/app/login.tsx
new file mode 100644
index 0000000..4b05b74
--- /dev/null
+++ b/app/login.tsx
@@ -0,0 +1,64 @@
+import { useEffect } from 'react';
+import { View, StyleSheet, Text } from 'react-native';
+import { GoogleSignin, GoogleSigninButton } from '@react-native-google-signin/google-signin';
+import Constants from 'expo-constants';
+import { useAuth } from '@/hooks/useAuth';
+
+export default function LoginScreen() {
+ const { setUser } = useAuth();
+
+ useEffect(() => {
+ const webClientId =
+ Constants.expoConfig?.extra?.googleWebClientId ||
+ // Expo public envs are embedded at bundle-time
+ (process.env.EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID as string | undefined);
+ if (!webClientId) {
+ console.warn(
+ 'Google Web Client ID is missing. Set GOOGLE_WEB_CLIENT_ID (app.config.js) or EXPO_PUBLIC_GOOGLE_WEB_CLIENT_ID in your .env/EAS env. Falling back to no offline access.'
+ );
+ // Configure without offline access to avoid runtime error
+ GoogleSignin.configure({});
+ return;
+ }
+ GoogleSignin.configure({
+ webClientId,
+ // offlineAccess allows getting a refresh token; requires a Server (Web) OAuth client ID
+ offlineAccess: true,
+ });
+ }, []);
+
+ const signIn = async () => {
+ try {
+ await GoogleSignin.hasPlayServices({ showPlayServicesUpdateDialog: true });
+ const result = await GoogleSignin.signIn();
+
+ // @react-native-google-signin/google-signin returns an object with idToken and user
+ if (result?.idToken) {
+ setUser({ idToken: result.idToken, user: result.user });
+ fetch('https://new.codebuilder.org/api/auth/google', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ idToken: result.idToken }),
+ }).catch((e) => console.error('Auth callback error:', e));
+ } else {
+ console.warn('Google sign in did not return an idToken.');
+ }
+ } catch (e: any) {
+ // Log richer error info for troubleshooting (code, description)
+ const code = e?.code || e?.statusCodes || 'UNKNOWN';
+ console.error('Google sign in error:', code, e?.message || e);
+ }
+ };
+
+ return (
+
+ Login
+
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: { flex: 1, justifyContent: 'center', alignItems: 'center' },
+ title: { fontSize: 24, fontWeight: '600', marginBottom: 16 },
+});
diff --git a/components/LogViewer.tsx b/components/LogViewer.tsx
index 48eb37e..6efd7bf 100644
--- a/components/LogViewer.tsx
+++ b/components/LogViewer.tsx
@@ -1,45 +1,66 @@
-import React, { useEffect, useState } from "react";
-import { ScrollView, Text, View, Button } from "react-native";
-import { ConsoleView } from "react-native-console-view";
+import React, { useEffect, useRef, useState } from 'react';
+import { ScrollView, Text, View, Button } from 'react-native';
+import { ConsoleView } from 'react-native-console-view';
const LogViewer = () => {
- const [logs, setLogs] = useState([]);
+ const [logs, setLogs] = useState([]);
- useEffect(() => {
- const originalLog = console.log;
- console.log = (...args) => {
- setLogs((prevLogs) => [...prevLogs, args.map(String).join(" ")]);
- originalLog(...args);
- };
+ const flushingRef = useRef(false);
+ useEffect(() => {
+ const originalLog = console.log;
+ const originalError = console.error;
+ const originalWarn = console.warn;
- return () => {
- console.log = originalLog; // Restore original console.log on unmount
- };
- }, []);
+ const enqueue = (level: 'log' | 'error' | 'warn', args: any[]) => {
+ // Defer state update to microtask to avoid setState during another component's render
+ Promise.resolve().then(() => {
+ setLogs((prev) => [...prev, `[${level.toUpperCase()}] ${args.map(String).join(' ')}`]);
+ });
+ };
- return (
-
-
-
- {logs.map((log, index) => (
-
- {log}
-
- ))}
-
-
- );
+ console.log = (...args) => {
+ enqueue('log', args);
+ originalLog(...args);
+ };
+ console.error = (...args) => {
+ enqueue('error', args);
+ originalError(...args);
+ };
+ console.warn = (...args) => {
+ enqueue('warn', args);
+ originalWarn(...args);
+ };
+
+ return () => {
+ console.log = originalLog;
+ console.error = originalError;
+ console.warn = originalWarn;
+ };
+ }, []);
+
+ return (
+
+
+
+ {logs.map((log, index) => (
+
+ {log}
+
+ ))}
+
+
+ );
};
export default LogViewer;
diff --git a/google-services.json b/google-services.json
index 7497b6b..5ad11ed 100644
--- a/google-services.json
+++ b/google-services.json
@@ -12,7 +12,12 @@
"package_name": "com.digitalnomad91.codebuilderadmin"
}
},
- "oauth_client": [],
+ "oauth_client": [
+ {
+ "client_id": "906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ],
"api_key": [
{
"current_key": "AIzaSyDdDNSdl2ukCEpTy2tZ2IHz1lD3UiftQFo"
@@ -20,7 +25,12 @@
],
"services": {
"appinvite_service": {
- "other_platform_oauth_client": []
+ "other_platform_oauth_client": [
+ {
+ "client_id": "906784991953-aoq1tgiac6ge9aqv4kguai26265393h9.apps.googleusercontent.com",
+ "client_type": 3
+ }
+ ]
}
}
}
diff --git a/hooks/useAuth.ts b/hooks/useAuth.ts
new file mode 100644
index 0000000..67710ca
--- /dev/null
+++ b/hooks/useAuth.ts
@@ -0,0 +1,28 @@
+// hooks/useAuth.ts
+import { useContext } from "react";
+import { AuthContext } from "@/providers/AuthProvider";
+
+export interface AuthUser {
+ idToken: string;
+ user: {
+ id: string;
+ name: string | null;
+ email: string;
+ photo: string | null;
+ familyName: string | null;
+ givenName: string | null;
+ };
+}
+
+export type AuthContextType = {
+ user: AuthUser | null;
+ setUser: React.Dispatch>;
+};
+
+export function useAuth(): AuthContextType {
+ const ctx = useContext(AuthContext);
+ if (!ctx) {
+ throw new Error("useAuth must be used within ");
+ }
+ return ctx;
+}
diff --git a/hooks/usePushNotifications.ts b/hooks/usePushNotifications.ts
index 6f756de..0a91524 100644
--- a/hooks/usePushNotifications.ts
+++ b/hooks/usePushNotifications.ts
@@ -1,102 +1,92 @@
-import { useEffect, useState } from "react";
-import {
- handleForegroundNotification,
- handleBackgroundNotifications,
- registerForPushNotificationsAsync,
-} from "../utils/notifications.utils";
+import { useEffect, useState } from 'react';
+import { handleForegroundNotification, handleBackgroundNotifications, registerForPushNotificationsAsync } from '../utils/notifications.utils';
//import messaging from "@react-native-firebase/messaging";
-import * as Notifications from "expo-notifications";
-import { router } from "expo-router";
-import { getFirebaseApp } from "@/utils/firebase.utils";
+import * as Notifications from 'expo-notifications';
+import { router } from 'expo-router';
+import { getFirebaseApp } from '@/utils/firebase.utils';
let globalFcmToken: string | null = null;
export const usePushNotifications = () => {
- const [fcmToken, setFcmToken] = useState(globalFcmToken);
+ const [fcmToken, setFcmToken] = useState(globalFcmToken);
- useEffect(() => {
- const initializeNotifications = async () => {
- getFirebaseApp();
+ useEffect(() => {
+ const initializeNotifications = async () => {
+ getFirebaseApp();
- // Register for push notifications and get the push token
- registerForPushNotificationsAsync().then(async (token) => {
- if (token) {
- console.log(
- "Finished registering for Notifications - FCM Token:",
- token
- );
- setFcmToken(token); // Store the push token
- globalFcmToken = token; // Update the global token
- }
- });
+ // Register for push notifications and get the push token
+ registerForPushNotificationsAsync().then(async (token) => {
+ if (token) {
+ console.log('Finished registering for Notifications - FCM Token:', token);
+ setFcmToken(token); // Store the push token
+ globalFcmToken = token; // Update the global token
+ }
+ });
- // const hasPermission = await requestPermission();
- // if (hasPermission) {
- // const token = await messaging().getToken();
- // console.log("FCM Token:", token);
+ // const hasPermission = await requestPermission();
+ // if (hasPermission) {
+ // const token = await messaging().getToken();
+ // console.log("FCM Token:", token);
- // // Check if the token is already saved globally
- // if (token && token !== globalFcmToken) {
- // globalFcmToken = token; // Store in a global variable
- // setFcmToken(token);
+ // // Check if the token is already saved globally
+ // if (token && token !== globalFcmToken) {
+ // globalFcmToken = token; // Store in a global variable
+ // setFcmToken(token);
- // // Save the token to the server
- // try {
- // await savePushToken(token);
- // } catch (error) {
- // console.error("Error saving push token:", error);
- // }
- // }
- // }
- };
+ // // Save the token to the server
+ // try {
+ // await savePushToken(token);
+ // } catch (error) {
+ // console.error("Error saving push token:", error);
+ // }
+ // }
+ // }
+ };
- initializeNotifications();
- handleBackgroundNotifications();
- const unsubscribe = handleForegroundNotification();
+ initializeNotifications();
+ handleBackgroundNotifications();
+ const unsubscribe = handleForegroundNotification();
- // Set a global notification handler
- Notifications.setNotificationHandler({
- handleNotification: async () => ({
- shouldShowAlert: true,
- shouldPlaySound: true,
- shouldSetBadge: true,
- shouldShowBanner: true,
- shouldShowList: true,
- }),
- });
+ // Set a global notification handler
+ Notifications.setNotificationHandler({
+ handleNotification: async () => ({
+ shouldPlaySound: true,
+ shouldSetBadge: true,
+ shouldShowBanner: true,
+ shouldShowList: true,
+ }),
+ });
- return () => {
- unsubscribe();
- };
- }, []);
+ return () => {
+ unsubscribe();
+ };
+ }, []);
- return { fcmToken };
+ return { fcmToken };
};
export const getFcmToken = () => globalFcmToken;
export const useNotificationObserver = () => {
- useEffect(() => {
- const redirectToUrl = (notification: Notifications.Notification) => {
- const url = notification.request.content.data?.url;
- if (typeof url === "string") {
- router.push(url as any);
- }
- };
+ useEffect(() => {
+ const redirectToUrl = (notification: Notifications.Notification) => {
+ const url = notification.request.content.data?.url;
+ if (typeof url === 'string') {
+ router.push(url as any);
+ }
+ };
- Notifications.getLastNotificationResponseAsync().then((response) => {
- if (response?.notification) {
- redirectToUrl(response.notification);
- }
- });
+ Notifications.getLastNotificationResponseAsync().then((response) => {
+ if (response?.notification) {
+ redirectToUrl(response.notification);
+ }
+ });
- const subscription = Notifications.addNotificationResponseReceivedListener(
- (response) => {
- redirectToUrl(response.notification);
- }
- );
+ const subscription = Notifications.addNotificationResponseReceivedListener((response) => {
+ redirectToUrl(response.notification);
+ });
- return () => {
- subscription.remove();
- };
- }, []);
+ return () => {
+ subscription.remove();
+ };
+ }, []);
};
diff --git a/package-lock.json b/package-lock.json
index 1e46666..54fb9a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -17,6 +17,7 @@
"@react-native-firebase/app": "^22.2.1",
"@react-native-firebase/auth": "^22.2.1",
"@react-native-firebase/messaging": "^22.2.1",
+ "@react-native-google-signin/google-signin": "^15.0.0",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"babel-preset-expo": "~13.0.0",
@@ -115,30 +116,30 @@
}
},
"node_modules/@babel/compat-data": {
- "version": "7.27.5",
- "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.5.tgz",
- "integrity": "sha512-KiRAp/VoJaWkkte84TvUd9qjdbZAdiqyvMxrGl1N6vzFogKmaLgoM3L1kgtLicp2HP5fBJS8JrZKLVIZGVJAVg==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.27.7.tgz",
+ "integrity": "sha512-xgu/ySj2mTiUFmdE9yCMfBxLp4DHd5DwmbbD05YAuICfodYT3VvRxbrh81LGQ/8UpSdtMdfKMn3KouYDX59DGQ==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
- "version": "7.27.4",
- "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.4.tgz",
- "integrity": "sha512-bXYxrXFubeYdvB0NhD/NBB3Qi6aZeV20GOWVI47t2dkecCEoneR4NPVcb7abpXDEvejgrUfFtG6vG/zxAKmg+g==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.27.7.tgz",
+ "integrity": "sha512-BU2f9tlKQ5CAthiMIgpzAh4eDTLWo1mqi9jqE2OxMG0E/OM199VJt2q8BztTxpnSW0i1ymdwLXRJnYzvDM5r2w==",
"license": "MIT",
"dependencies": {
"@ampproject/remapping": "^2.2.0",
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.3",
+ "@babel/generator": "^7.27.5",
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-module-transforms": "^7.27.3",
- "@babel/helpers": "^7.27.4",
- "@babel/parser": "^7.27.4",
+ "@babel/helpers": "^7.27.6",
+ "@babel/parser": "^7.27.7",
"@babel/template": "^7.27.2",
- "@babel/traverse": "^7.27.4",
- "@babel/types": "^7.27.3",
+ "@babel/traverse": "^7.27.7",
+ "@babel/types": "^7.27.7",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
@@ -272,16 +273,16 @@
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.4.tgz",
- "integrity": "sha512-jljfR1rGnXXNWnmQg2K3+bvhkxB51Rl32QRaOTuwwjviGrHzIbSc8+x9CpraDtbT7mfyjXObULP4w/adunNwAw==",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.5.tgz",
+ "integrity": "sha512-uJnGFcPsWQK8fvjgGP5LZUZZsYGIoPeRjSF5PGwrelYgq7Q15/Ft9NGFp1zglwgIv//W0uG4BevRuSJRyylZPg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-compilation-targets": "^7.22.6",
- "@babel/helper-plugin-utils": "^7.22.5",
- "debug": "^4.1.1",
+ "@babel/helper-compilation-targets": "^7.27.2",
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "debug": "^4.4.1",
"lodash.debounce": "^4.0.8",
- "resolve": "^1.14.2"
+ "resolve": "^1.22.10"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -539,12 +540,12 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.27.5",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.5.tgz",
- "integrity": "sha512-OsQd175SxWkGlzbny8J3K8TnnDD0N3lrIUtB92xwyRpzaenGZhxDvxN/JgU00U3CDZNj9tPuDJ5H0WS4Nt3vKg==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.7.tgz",
+ "integrity": "sha512-qnzXzDXdr/po3bOTbTIQZ7+TxNKxpkN5IifVLXS+r7qwynkZfPyjZfE7hCXbo7IoO9TNcSyibgONsf2HauUd3Q==",
"license": "MIT",
"dependencies": {
- "@babel/types": "^7.27.3"
+ "@babel/types": "^7.27.7"
},
"bin": {
"parser": "bin/babel-parser.js"
@@ -945,16 +946,16 @@
}
},
"node_modules/@babel/plugin-transform-classes": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.1.tgz",
- "integrity": "sha512-7iLhfFAubmpeJe/Wo2TVuDrykh/zlWXLzPNdL0Jqn/Xu8R3QQ8h9ff8FQoISZOsw74/HFqFI7NX63HN7QFIHKA==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.27.7.tgz",
+ "integrity": "sha512-CuLkokN1PEZ0Fsjtq+001aog/C2drDK9nTfK/NRK0n6rBin6cBrvM+zfQjDE+UllhR6/J4a6w8Xq9i4yi3mQrw==",
"license": "MIT",
"dependencies": {
- "@babel/helper-annotate-as-pure": "^7.27.1",
- "@babel/helper-compilation-targets": "^7.27.1",
+ "@babel/helper-annotate-as-pure": "^7.27.3",
+ "@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-replace-supers": "^7.27.1",
- "@babel/traverse": "^7.27.1",
+ "@babel/traverse": "^7.27.7",
"globals": "^11.1.0"
},
"engines": {
@@ -981,12 +982,13 @@
}
},
"node_modules/@babel/plugin-transform-destructuring": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.3.tgz",
- "integrity": "sha512-s4Jrok82JpiaIprtY2nHsYmrThKvvwgHwjgd7UMiYhZaN0asdXNLr0y+NjTfkA7SyQE5i2Fb7eawUOZmLvyqOA==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.27.7.tgz",
+ "integrity": "sha512-pg3ZLdIKWCP0CrJm0O4jYjVthyBeioVfvz9nwt6o5paUxsgJ/8GucSMAIaj6M7xA4WY+SrvtGu2LijzkdyecWQ==",
"license": "MIT",
"dependencies": {
- "@babel/helper-plugin-utils": "^7.27.1"
+ "@babel/helper-plugin-utils": "^7.27.1",
+ "@babel/traverse": "^7.27.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1152,15 +1154,16 @@
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
- "version": "7.27.3",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.3.tgz",
- "integrity": "sha512-7ZZtznF9g4l2JCImCo5LNKFHB5eXnN39lLtLY5Tg+VkR0jwOt7TBciMckuiQIOIW7L5tkQOCh3bVGYeXgMx52Q==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.27.7.tgz",
+ "integrity": "sha512-201B1kFTWhckclcXpWHc8uUpYziDX/Pl4rxl0ZX0DiCZ3jknwfSUALL3QCYeeXXB37yWxJbo+g+Vfq8pAaHi3w==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.2",
"@babel/helper-plugin-utils": "^7.27.1",
- "@babel/plugin-transform-destructuring": "^7.27.3",
- "@babel/plugin-transform-parameters": "^7.27.1"
+ "@babel/plugin-transform-destructuring": "^7.27.7",
+ "@babel/plugin-transform-parameters": "^7.27.7",
+ "@babel/traverse": "^7.27.7"
},
"engines": {
"node": ">=6.9.0"
@@ -1201,9 +1204,9 @@
}
},
"node_modules/@babel/plugin-transform-parameters": {
- "version": "7.27.1",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.1.tgz",
- "integrity": "sha512-018KRk76HWKeZ5l4oTj2zPpSh+NbGdt0st5S6x0pga6HgrjBOJb24mMDHorFopOOd6YHkLgOZ+zaCjZGPO4aKg==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.27.7.tgz",
+ "integrity": "sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
@@ -1546,16 +1549,16 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.27.4",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
- "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz",
+ "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.3",
- "@babel/parser": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/parser": "^7.27.7",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.27.3",
+ "@babel/types": "^7.27.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1565,16 +1568,16 @@
},
"node_modules/@babel/traverse--for-generate-function-map": {
"name": "@babel/traverse",
- "version": "7.27.4",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.4.tgz",
- "integrity": "sha512-oNcu2QbHqts9BtOWJosOVJapWjBDSxGCpFvikNR5TGDYDQf3JwpIoMzIKrvfoti93cLfPJEG4tH9SPVeyCGgdA==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.7.tgz",
+ "integrity": "sha512-X6ZlfR/O/s5EQ/SnUSLzr+6kGnkg8HXGMzpgsMsrJVcfDtH1vIp6ctCN4eZ1LS5c0+te5Cb6Y514fASjMRJ1nw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.27.1",
- "@babel/generator": "^7.27.3",
- "@babel/parser": "^7.27.4",
+ "@babel/generator": "^7.27.5",
+ "@babel/parser": "^7.27.7",
"@babel/template": "^7.27.2",
- "@babel/types": "^7.27.3",
+ "@babel/types": "^7.27.7",
"debug": "^4.3.1",
"globals": "^11.1.0"
},
@@ -1583,9 +1586,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.27.6",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.6.tgz",
- "integrity": "sha512-ETyHEk2VHHvl9b9jZP5IHPavHYk57EhanlRRuae9XCpb/j5bDCbPPMOBfCWhnl/7EDJz0jEMCi/RhccCE8r1+Q==",
+ "version": "7.27.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.7.tgz",
+ "integrity": "sha512-8OLQgDScAOHXnAz2cV+RfzzNMipuLVBz2biuAJFMV9bfkNf393je3VM8CLkjQodW5+iWsSJdSgSWT6rsZoXHPw==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
@@ -1977,9 +1980,9 @@
}
},
"node_modules/@expo/cli/node_modules/ws": {
- "version": "8.18.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
- "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
@@ -2543,9 +2546,9 @@
}
},
"node_modules/@expo/prebuild-config": {
- "version": "9.0.7",
- "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.7.tgz",
- "integrity": "sha512-1w5MBp6NdF51gPGp0HsCZt0QC82hZWo37wI9HfxhdQF/sN/92Mh4t30vaY7gjHe71T5QNyab00oxZH/wP0MDgQ==",
+ "version": "9.0.8",
+ "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-9.0.8.tgz",
+ "integrity": "sha512-vzORt1zrgjIYOKAmHk0SUka0rlYo0AIZpOF6019ms2XaQnxyvcQACz1BxTk2++fTAzSAxerufNQOm2owPcAz+g==",
"license": "MIT",
"dependencies": {
"@expo/config": "~11.0.10",
@@ -2638,15 +2641,15 @@
}
},
"node_modules/@firebase/ai": {
- "version": "1.4.0",
- "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.0.tgz",
- "integrity": "sha512-wvF33gtU6TXb6Co8TEC1pcl4dnVstYmRE/vs9XjUGE7he7Sgf5TqSu+EoXk/fuzhw5tKr1LC5eG9KdYFM+eosw==",
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-1.4.1.tgz",
+ "integrity": "sha512-bcusQfA/tHjUjBTnMx6jdoPMpDl3r8K15Z+snHz9wq0Foox0F/V+kNLXucEOHoTL2hTc9l+onZCyBJs2QoIC3g==",
"license": "Apache-2.0",
"dependencies": {
"@firebase/app-check-interop-types": "0.3.3",
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2658,15 +2661,15 @@
}
},
"node_modules/@firebase/analytics": {
- "version": "0.10.16",
- "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.16.tgz",
- "integrity": "sha512-cMtp19He7Fd6uaj/nDEul+8JwvJsN8aRSJyuA1QN3QrKvfDDp+efjVurJO61sJpkVftw9O9nNMdhFbRcTmTfRQ==",
+ "version": "0.10.17",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.17.tgz",
+ "integrity": "sha512-n5vfBbvzduMou/2cqsnKrIes4auaBjdhg8QNA2ZQZ59QgtO2QiwBaXQZQE4O4sgB0Ds1tvLgUUkY+pwzu6/xEg==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/installations": "0.6.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/installations": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -2674,15 +2677,15 @@
}
},
"node_modules/@firebase/analytics-compat": {
- "version": "0.2.22",
- "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.22.tgz",
- "integrity": "sha512-VogWHgwkdYhjWKh8O1XU04uPrRaiDihkWvE/EMMmtWtaUtVALnpLnUurc3QtSKdPnvTz5uaIGKlW84DGtSPFbw==",
+ "version": "0.2.23",
+ "resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.23.tgz",
+ "integrity": "sha512-3AdO10RN18G5AzREPoFgYhW6vWXr3u+OYQv6pl3CX6Fky8QRk0AHurZlY3Q1xkXO0TDxIsdhO3y65HF7PBOJDw==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/analytics": "0.10.16",
+ "@firebase/analytics": "0.10.17",
"@firebase/analytics-types": "0.8.3",
- "@firebase/component": "0.6.17",
- "@firebase/util": "1.12.0",
+ "@firebase/component": "0.6.18",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -2696,14 +2699,14 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/app": {
- "version": "0.13.1",
- "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.1.tgz",
- "integrity": "sha512-0O33PKrXLoIWkoOO5ByFaLjZehBctSYWnb+xJkIdx2SKP/K9l1UPFXPwASyrOIqyY3ws+7orF/1j7wI5EKzPYQ==",
+ "version": "0.13.2",
+ "resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.13.2.tgz",
+ "integrity": "sha512-jwtMmJa1BXXDCiDx1vC6SFN/+HfYG53UkfJa6qeN5ogvOunzbFDO3wISZy5n9xgYFUrEP6M7e8EG++riHNTv9w==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"idb": "7.1.1",
"tslib": "^2.1.0"
},
@@ -2712,14 +2715,14 @@
}
},
"node_modules/@firebase/app-check": {
- "version": "0.10.0",
- "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.0.tgz",
- "integrity": "sha512-AZlRlVWKcu8BH4Yf8B5EI8sOi2UNGTS8oMuthV45tbt6OVUTSQwFPIEboZzhNJNKY+fPsg7hH8vixUWFZ3lrhw==",
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.10.1.tgz",
+ "integrity": "sha512-MgNdlms9Qb0oSny87pwpjKush9qUwCJhfmTJHDfrcKo4neLGiSeVE4qJkzP7EQTIUFKp84pbTxobSAXkiuQVYQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2730,16 +2733,16 @@
}
},
"node_modules/@firebase/app-check-compat": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.25.tgz",
- "integrity": "sha512-3zrsPZWAKfV7DVC20T2dgfjzjtQnSJS65OfMOiddMUtJL1S5i0nAZKsdX0bOEvvrd0SBIL8jYnfpfDeQRnhV3w==",
+ "version": "0.3.26",
+ "resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.3.26.tgz",
+ "integrity": "sha512-PkX+XJMLDea6nmnopzFKlr+s2LMQGqdyT2DHdbx1v1dPSqOol2YzgpgymmhC67vitXVpNvS3m/AiWQWWhhRRPQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/app-check": "0.10.0",
+ "@firebase/app-check": "0.10.1",
"@firebase/app-check-types": "0.5.3",
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2762,15 +2765,15 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/app-compat": {
- "version": "0.4.1",
- "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.1.tgz",
- "integrity": "sha512-9VGjnY23Gc1XryoF/ABWtZVJYnaPOnjHM7dsqq9YALgKRtxI1FryvELUVkDaEIUf4In2bfkb9ZENF1S9M273Dw==",
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.4.2.tgz",
+ "integrity": "sha512-LssbyKHlwLeiV8GBATyOyjmHcMpX/tFjzRUCS1jnwGAew1VsBB4fJowyS5Ud5LdFbYpJeS+IQoC+RQxpK7eH3Q==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/app": "0.13.1",
- "@firebase/component": "0.6.17",
+ "@firebase/app": "0.13.2",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2784,14 +2787,14 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/auth": {
- "version": "1.10.7",
- "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.7.tgz",
- "integrity": "sha512-77o0aBKCfchdL1gkahARdawHyYefh+wRYn7o60tbwW6bfJNq2idbrRb3WSYCT4yBKWL0+9kKdwxBHPZ6DEiB+g==",
+ "version": "1.10.8",
+ "resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.10.8.tgz",
+ "integrity": "sha512-GpuTz5ap8zumr/ocnPY57ZanX02COsXloY6Y/2LYPAuXYiaJRf6BAGDEdRq1BMjP93kqQnKNuKZUTMZbQ8MNYA==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2808,15 +2811,15 @@
}
},
"node_modules/@firebase/auth-compat": {
- "version": "0.5.27",
- "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.27.tgz",
- "integrity": "sha512-axZx/MgjNO7uPA8/nMQiuVotGCngUFMppt5w0pxFIoIPD0kac0bsFdSEh5S2ttuEE0Aq1iUB6Flzwn+wvMgXnQ==",
+ "version": "0.5.28",
+ "resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.5.28.tgz",
+ "integrity": "sha512-HpMSo/cc6Y8IX7bkRIaPPqT//Jt83iWy5rmDWeThXQCAImstkdNo3giFLORJwrZw2ptiGkOij64EH1ztNJzc7Q==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/auth": "1.10.7",
+ "@firebase/auth": "1.10.8",
"@firebase/auth-types": "0.13.0",
- "@firebase/component": "0.6.17",
- "@firebase/util": "1.12.0",
+ "@firebase/component": "0.6.18",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2843,12 +2846,12 @@
}
},
"node_modules/@firebase/component": {
- "version": "0.6.17",
- "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.17.tgz",
- "integrity": "sha512-M6DOg7OySrKEFS8kxA3MU5/xc37fiOpKPMz6cTsMUcsuKB6CiZxxNAvgFta8HGRgEpZbi8WjGIj6Uf+TpOhyzg==",
+ "version": "0.6.18",
+ "resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.6.18.tgz",
+ "integrity": "sha512-n28kPCkE2dL2U28fSxZJjzPPVpKsQminJ6NrzcKXAI0E/lYC8YhfwpyllScqVEvAI3J2QgJZWYgrX+1qGI+SQQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2856,15 +2859,15 @@
}
},
"node_modules/@firebase/data-connect": {
- "version": "0.3.9",
- "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.9.tgz",
- "integrity": "sha512-B5tGEh5uQrQeH0i7RvlU8kbZrKOJUmoyxVIX4zLA8qQJIN6A7D+kfBlGXtSwbPdrvyaejcRPcbOtqsDQ9HPJKw==",
+ "version": "0.3.10",
+ "resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.3.10.tgz",
+ "integrity": "sha512-VMVk7zxIkgwlVQIWHOKFahmleIjiVFwFOjmakXPd/LDgaB/5vzwsB5DWIYo+3KhGxWpidQlR8geCIn39YflJIQ==",
"license": "Apache-2.0",
"dependencies": {
"@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -2872,16 +2875,16 @@
}
},
"node_modules/@firebase/database": {
- "version": "1.0.19",
- "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.19.tgz",
- "integrity": "sha512-khE+MIYK+XlIndVn/7mAQ9F1fwG5JHrGKaG72hblCC6JAlUBDd3SirICH6SMCf2PQ0iYkruTECth+cRhauacyQ==",
+ "version": "1.0.20",
+ "resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.0.20.tgz",
+ "integrity": "sha512-H9Rpj1pQ1yc9+4HQOotFGLxqAXwOzCHsRSRjcQFNOr8lhUt6LeYjf0NSRL04sc4X0dWe8DsCvYKxMYvFG/iOJw==",
"license": "Apache-2.0",
"dependencies": {
"@firebase/app-check-interop-types": "0.3.3",
"@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"faye-websocket": "0.11.4",
"tslib": "^2.1.0"
},
@@ -2890,16 +2893,16 @@
}
},
"node_modules/@firebase/database-compat": {
- "version": "2.0.10",
- "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.10.tgz",
- "integrity": "sha512-3sjl6oGaDDYJw/Ny0E5bO6v+KM3KoD4Qo/sAfHGdRFmcJ4QnfxOX9RbG9+ce/evI3m64mkPr24LlmTDduqMpog==",
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.0.11.tgz",
+ "integrity": "sha512-itEsHARSsYS95+udF/TtIzNeQ0Uhx4uIna0sk4E0wQJBUnLc/G1X6D7oRljoOuwwCezRLGvWBRyNrugv/esOEw==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/database": "1.0.19",
- "@firebase/database-types": "1.0.14",
+ "@firebase/component": "0.6.18",
+ "@firebase/database": "1.0.20",
+ "@firebase/database-types": "1.0.15",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2907,24 +2910,24 @@
}
},
"node_modules/@firebase/database-types": {
- "version": "1.0.14",
- "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.14.tgz",
- "integrity": "sha512-8a0Q1GrxM0akgF0RiQHliinhmZd+UQPrxEmUv7MnQBYfVFiLtKOgs3g6ghRt/WEGJHyQNslZ+0PocIwNfoDwKw==",
+ "version": "1.0.15",
+ "resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.15.tgz",
+ "integrity": "sha512-XWHJ0VUJ0k2E9HDMlKxlgy/ZuTa9EvHCGLjaKSUvrQnwhgZuRU5N3yX6SZ+ftf2hTzZmfRkv+b3QRvGg40bKNw==",
"license": "Apache-2.0",
"dependencies": {
"@firebase/app-types": "0.9.3",
- "@firebase/util": "1.12.0"
+ "@firebase/util": "1.12.1"
}
},
"node_modules/@firebase/firestore": {
- "version": "4.7.17",
- "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.7.17.tgz",
- "integrity": "sha512-YhXWA7HlSnekExhZ5u4i0e+kpPxsh/qMrzeNDgsAva71JXK8OOuOx+yLyYBFhmu3Hr5JJDO2fsZA/wrWoQYHDg==",
+ "version": "4.8.0",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.8.0.tgz",
+ "integrity": "sha512-QSRk+Q1/CaabKyqn3C32KSFiOdZpSqI9rpLK5BHPcooElumOBooPFa6YkDdiT+/KhJtel36LdAacha9BptMj2A==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"@firebase/webchannel-wrapper": "1.0.3",
"@grpc/grpc-js": "~1.9.0",
"@grpc/proto-loader": "^0.7.8",
@@ -2938,15 +2941,15 @@
}
},
"node_modules/@firebase/firestore-compat": {
- "version": "0.3.52",
- "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.52.tgz",
- "integrity": "sha512-nzt3Sag+EBdm1Jkw/FnnKBPk0LpUUxOlMHMADPBXYhhXrLszxn1+vb64nJsbgRIHfsCn+rg8gyGrb+8frzXrjg==",
+ "version": "0.3.53",
+ "resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.3.53.tgz",
+ "integrity": "sha512-qI3yZL8ljwAYWrTousWYbemay2YZa+udLWugjdjju2KODWtLG94DfO4NALJgPLv8CVGcDHNFXoyQexdRA0Cz8Q==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/firestore": "4.7.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/firestore": "4.8.0",
"@firebase/firestore-types": "3.0.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2967,16 +2970,16 @@
}
},
"node_modules/@firebase/functions": {
- "version": "0.12.8",
- "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.8.tgz",
- "integrity": "sha512-p+ft6dQW0CJ3BLLxeDb5Hwk9ARw01kHTZjLqiUdPRzycR6w7Z75ThkegNmL6gCss3S0JEpldgvehgZ3kHybVhA==",
+ "version": "0.12.9",
+ "resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.12.9.tgz",
+ "integrity": "sha512-FG95w6vjbUXN84Ehezc2SDjGmGq225UYbHrb/ptkRT7OTuCiQRErOQuyt1jI1tvcDekdNog+anIObihNFz79Lg==",
"license": "Apache-2.0",
"dependencies": {
"@firebase/app-check-interop-types": "0.3.3",
"@firebase/auth-interop-types": "0.2.4",
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -2987,15 +2990,15 @@
}
},
"node_modules/@firebase/functions-compat": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.25.tgz",
- "integrity": "sha512-V0JKUw5W/7aznXf9BQ8LIYHCX6zVCM8Hdw7XUQ/LU1Y9TVP8WKRCnPB/qdPJ0xGjWWn7fhtwIYbgEw/syH4yTQ==",
+ "version": "0.3.26",
+ "resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.3.26.tgz",
+ "integrity": "sha512-A798/6ff5LcG2LTWqaGazbFYnjBW8zc65YfID/en83ALmkhu2b0G8ykvQnLtakbV9ajrMYPn7Yc/XcYsZIUsjA==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/functions": "0.12.8",
+ "@firebase/component": "0.6.18",
+ "@firebase/functions": "0.12.9",
"@firebase/functions-types": "0.6.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -3012,13 +3015,13 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/installations": {
- "version": "0.6.17",
- "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.17.tgz",
- "integrity": "sha512-zfhqCNJZRe12KyADtRrtOj+SeSbD1H/K8J24oQAJVv/u02eQajEGlhZtcx9Qk7vhGWF5z9dvIygVDYqLL4o1XQ==",
+ "version": "0.6.18",
+ "resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.18.tgz",
+ "integrity": "sha512-NQ86uGAcvO8nBRwVltRL9QQ4Reidc/3whdAasgeWCPIcrhOKDuNpAALa6eCVryLnK14ua2DqekCOX5uC9XbU/A==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/util": "1.12.0",
+ "@firebase/component": "0.6.18",
+ "@firebase/util": "1.12.1",
"idb": "7.1.1",
"tslib": "^2.1.0"
},
@@ -3027,15 +3030,15 @@
}
},
"node_modules/@firebase/installations-compat": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.17.tgz",
- "integrity": "sha512-J7afeCXB7yq25FrrJAgbx8mn1nG1lZEubOLvYgG7ZHvyoOCK00sis5rj7TgDrLYJgdj/SJiGaO1BD3BAp55TeA==",
+ "version": "0.2.18",
+ "resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.18.tgz",
+ "integrity": "sha512-aLFohRpJO5kKBL/XYL4tN+GdwEB/Q6Vo9eZOM/6Kic7asSUgmSfGPpGUZO1OAaSRGwF4Lqnvi1f/f9VZnKzChw==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/installations": "0.6.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/installations": "0.6.18",
"@firebase/installations-types": "0.5.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -3064,15 +3067,15 @@
}
},
"node_modules/@firebase/messaging": {
- "version": "0.12.21",
- "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.21.tgz",
- "integrity": "sha512-bYJ2Evj167Z+lJ1ach6UglXz5dUKY1zrJZd15GagBUJSR7d9KfiM1W8dsyL0lDxcmhmA/sLaBYAAhF1uilwN0g==",
+ "version": "0.12.22",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.22.tgz",
+ "integrity": "sha512-GJcrPLc+Hu7nk+XQ70Okt3M1u1eRr2ZvpMbzbc54oTPJZySHcX9ccZGVFcsZbSZ6o1uqumm8Oc7OFkD3Rn1/og==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/installations": "0.6.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/installations": "0.6.18",
"@firebase/messaging-interop-types": "0.2.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"idb": "7.1.1",
"tslib": "^2.1.0"
},
@@ -3081,14 +3084,14 @@
}
},
"node_modules/@firebase/messaging-compat": {
- "version": "0.2.21",
- "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.21.tgz",
- "integrity": "sha512-1yMne+4BGLbHbtyu/VyXWcLiefUE1+K3ZGfVTyKM4BH4ZwDFRGoWUGhhx+tKRX4Tu9z7+8JN67SjnwacyNWK5g==",
+ "version": "0.2.22",
+ "resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.22.tgz",
+ "integrity": "sha512-5ZHtRnj6YO6f/QPa/KU6gryjmX4Kg33Kn4gRpNU6M1K47Gm8kcQwPkX7erRUYEH1mIWptfvjvXMHWoZaWjkU7A==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/messaging": "0.12.21",
- "@firebase/util": "1.12.0",
+ "@firebase/component": "0.6.18",
+ "@firebase/messaging": "0.12.22",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -3102,15 +3105,15 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/performance": {
- "version": "0.7.6",
- "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.6.tgz",
- "integrity": "sha512-AsOz74dSTlyQGlnnbLWXiHFAsrxhpssPOsFFi4HgOJ5DjzkK7ZdZ/E9uMPrwFoXJyMVoybGRuqsL/wkIbFITsA==",
+ "version": "0.7.7",
+ "resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.7.tgz",
+ "integrity": "sha512-JTlTQNZKAd4+Q5sodpw6CN+6NmwbY72av3Lb6wUKTsL7rb3cuBIhQSrslWbVz0SwK3x0ZNcqX24qtRbwKiv+6w==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/installations": "0.6.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/installations": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0",
"web-vitals": "^4.2.4"
},
@@ -3119,16 +3122,16 @@
}
},
"node_modules/@firebase/performance-compat": {
- "version": "0.2.19",
- "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.19.tgz",
- "integrity": "sha512-4cU0T0BJ+LZK/E/UwFcvpBCVdkStgBMQwBztM9fJPT6udrEUk3ugF5/HT+E2Z22FCXtIaXDukJbYkE/c3c6IHw==",
+ "version": "0.2.20",
+ "resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.20.tgz",
+ "integrity": "sha512-XkFK5NmOKCBuqOKWeRgBUFZZGz9SzdTZp4OqeUg+5nyjapTiZ4XoiiUL8z7mB2q+63rPmBl7msv682J3rcDXIQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/performance": "0.7.6",
+ "@firebase/performance": "0.7.7",
"@firebase/performance-types": "0.2.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -3142,15 +3145,15 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/remote-config": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.4.tgz",
- "integrity": "sha512-ZyLJRT46wtycyz2+opEkGaoFUOqRQjt/0NX1WfUISOMCI/PuVoyDjqGpq24uK+e8D5NknyTpiXCVq5dowhScmg==",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.6.5.tgz",
+ "integrity": "sha512-fU0c8HY0vrVHwC+zQ/fpXSqHyDMuuuglV94VF6Yonhz8Fg2J+KOowPGANM0SZkLvVOYpTeWp3ZmM+F6NjwWLnw==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/installations": "0.6.17",
+ "@firebase/component": "0.6.18",
+ "@firebase/installations": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -3158,16 +3161,16 @@
}
},
"node_modules/@firebase/remote-config-compat": {
- "version": "0.2.17",
- "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.17.tgz",
- "integrity": "sha512-KelsBD0sXSC0u3esr/r6sJYGRN6pzn3bYuI/6pTvvmZbjBlxQkRabHAVH6d+YhLcjUXKIAYIjZszczd1QJtOyA==",
+ "version": "0.2.18",
+ "resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.18.tgz",
+ "integrity": "sha512-YiETpldhDy7zUrnS8e+3l7cNs0sL7+tVAxvVYU0lu7O+qLHbmdtAxmgY+wJqWdW2c9nDvBFec7QiF58pEUu0qQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
+ "@firebase/component": "0.6.18",
"@firebase/logger": "0.4.4",
- "@firebase/remote-config": "0.6.4",
+ "@firebase/remote-config": "0.6.5",
"@firebase/remote-config-types": "0.4.0",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"peerDependencies": {
@@ -3181,13 +3184,13 @@
"license": "Apache-2.0"
},
"node_modules/@firebase/storage": {
- "version": "0.13.13",
- "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.13.tgz",
- "integrity": "sha512-E+MTNcBgpoAynicgVb2ZsHCuEOO4aAiUX5ahNwe/1dEyZpo2H4DwFqKQRNK/sdAIgBbjBwcfV2p0MdPFGIR0Ew==",
+ "version": "0.13.14",
+ "resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.13.14.tgz",
+ "integrity": "sha512-xTq5ixxORzx+bfqCpsh+o3fxOsGoDjC1nO0Mq2+KsOcny3l7beyBhP/y1u5T6mgsFQwI1j6oAkbT5cWdDBx87g==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/util": "1.12.0",
+ "@firebase/component": "0.6.18",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -3198,15 +3201,15 @@
}
},
"node_modules/@firebase/storage-compat": {
- "version": "0.3.23",
- "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.23.tgz",
- "integrity": "sha512-B/ufkT/R/tSvc2av+vP6ZYybGn26FwB9YVDYg/6Bro+5TN3VEkCeNmfnX3XLa2DSdXUTZAdWCbMxW0povGa4MA==",
+ "version": "0.3.24",
+ "resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.3.24.tgz",
+ "integrity": "sha512-XHn2tLniiP7BFKJaPZ0P8YQXKiVJX+bMyE2j2YWjYfaddqiJnROJYqSomwW6L3Y+gZAga35ONXUJQju6MB6SOQ==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/component": "0.6.17",
- "@firebase/storage": "0.13.13",
+ "@firebase/component": "0.6.18",
+ "@firebase/storage": "0.13.14",
"@firebase/storage-types": "0.8.3",
- "@firebase/util": "1.12.0",
+ "@firebase/util": "1.12.1",
"tslib": "^2.1.0"
},
"engines": {
@@ -3227,9 +3230,9 @@
}
},
"node_modules/@firebase/util": {
- "version": "1.12.0",
- "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.0.tgz",
- "integrity": "sha512-Z4rK23xBCwgKDqmzGVMef+Vb4xso2j5Q8OG0vVL4m4fA5ZjPMYQazu8OJJC3vtQRC3SQ/Pgx/6TPNVsCd70QRw==",
+ "version": "1.12.1",
+ "resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.12.1.tgz",
+ "integrity": "sha512-zGlBn/9Dnya5ta9bX/fgEoNC3Cp8s6h+uYPYaDieZsFOAdHP/ExzQ/eaDgxD3GOROdPkLKpvKY0iIzr9adle0w==",
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -4467,17 +4470,13 @@
}
},
"node_modules/@jridgewell/gen-mapping": {
- "version": "0.3.8",
- "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
- "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+ "version": "0.3.11",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.11.tgz",
+ "integrity": "sha512-C512c1ytBTio4MrpWKlJpyFHT6+qfFL8SZ58zBzJ1OOzUEjHeF1BtjY2fH7n4x/g2OV/KiiMLAivOp1DXmiMMw==",
"license": "MIT",
"dependencies": {
- "@jridgewell/set-array": "^1.2.1",
- "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
- },
- "engines": {
- "node": ">=6.0.0"
}
},
"node_modules/@jridgewell/resolve-uri": {
@@ -4489,19 +4488,10 @@
"node": ">=6.0.0"
}
},
- "node_modules/@jridgewell/set-array": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz",
- "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==",
- "license": "MIT",
- "engines": {
- "node": ">=6.0.0"
- }
- },
"node_modules/@jridgewell/source-map": {
- "version": "0.3.6",
- "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz",
- "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==",
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.9.tgz",
+ "integrity": "sha512-amBU75CKOOkcQLfyM6J+DnWwz41yTsWI7o8MQ003LwUIWb4NYX/evAblTx1oBBYJySqL/zHPxHXDw5ewpQaUFw==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
@@ -4509,15 +4499,15 @@
}
},
"node_modules/@jridgewell/sourcemap-codec": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
- "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==",
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.3.tgz",
+ "integrity": "sha512-AiR5uKpFxP3PjO4R19kQGIMwxyRyPuXmKEEy301V1C0+1rVjS94EZQXf1QKZYN8Q0YM+estSPhmx5JwNftv6nw==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
- "version": "0.3.25",
- "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
- "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+ "version": "0.3.28",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.28.tgz",
+ "integrity": "sha512-KNNHHwW3EIp4EDYOvYFGyIFfx36R2dNJYH4knnZlF8T5jdbD5Wx8xmSaQ2gP9URkJ04LGEtlcCtwArKcmFcwKw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -5488,6 +5478,22 @@
}
}
},
+ "node_modules/@react-native-google-signin/google-signin": {
+ "version": "15.0.0",
+ "resolved": "https://registry.npmjs.org/@react-native-google-signin/google-signin/-/google-signin-15.0.0.tgz",
+ "integrity": "sha512-oU49nE+Z9TT/WaO1K7BH/QL2Nx3d2T3I5PGcYdD8swKNtfhMt8qCX/3mOnNEzthTBPrR0CWFGo3LXpkYspalog==",
+ "license": "MIT",
+ "peerDependencies": {
+ "expo": ">=52.0.40",
+ "react": "*",
+ "react-native": "*"
+ },
+ "peerDependenciesMeta": {
+ "expo": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@react-native/assets-registry": {
"version": "0.79.4",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.79.4.tgz",
@@ -6140,9 +6146,9 @@
"license": "MIT"
},
"node_modules/@types/node": {
- "version": "24.0.4",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz",
- "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==",
+ "version": "24.0.7",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.7.tgz",
+ "integrity": "sha512-YIEUUr4yf8q8oQoXPpSlnvKNVKDQlPMWrmOcgzoduo7kvA2UF0/BwJ/eMKFTiTtkNL17I0M6Xe2tvwFU7be6iw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.8.0"
@@ -6463,22 +6469,22 @@
]
},
"node_modules/@urql/core": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.1.1.tgz",
- "integrity": "sha512-aGh024z5v2oINGD/In6rAtVKTm4VmQ2TxKQBAtk2ZSME5dunZFcjltw4p5ENQg+5CBhZ3FHMzl0Oa+rwqiWqlg==",
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@urql/core/-/core-5.2.0.tgz",
+ "integrity": "sha512-/n0ieD0mvvDnVAXEQgX/7qJiVcvYvNkOHeBvkwtylfjydar123caCXcl58PXFY11oU1oquJocVXHxLAbtv4x1A==",
"license": "MIT",
"dependencies": {
- "@0no-co/graphql.web": "^1.0.5",
+ "@0no-co/graphql.web": "^1.0.13",
"wonka": "^6.3.2"
}
},
"node_modules/@urql/exchange-retry": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.1.tgz",
- "integrity": "sha512-EEmtFu8JTuwsInqMakhLq+U3qN8ZMd5V3pX44q0EqD2imqTDsa8ikZqJ1schVrN8HljOdN+C08cwZ1/r5uIgLw==",
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/@urql/exchange-retry/-/exchange-retry-1.3.2.tgz",
+ "integrity": "sha512-TQMCz2pFJMfpNxmSfX1VSfTjwUIFx/mL+p1bnfM1xjjdla7Z+KnGMW/EhFbpckp3LyWAH4PgOsMwOMnIN+MBFg==",
"license": "MIT",
"dependencies": {
- "@urql/core": "^5.1.1",
+ "@urql/core": "^5.1.2",
"wonka": "^6.3.2"
},
"peerDependencies": {
@@ -7067,13 +7073,13 @@
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
- "version": "0.4.13",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.13.tgz",
- "integrity": "sha512-3sX/eOms8kd3q2KZ6DAhKPc0dgm525Gqq5NtWKZ7QYYZEv57OQ54KtblzJzH1lQF/eQxO8KjWGIK9IPUJNus5g==",
+ "version": "0.4.14",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.14.tgz",
+ "integrity": "sha512-Co2Y9wX854ts6U8gAAPXfn0GmAyctHuK8n0Yhfjd6t30g7yvKjspvvOo9yG+z52PZRgFErt7Ka2pYnXCjLKEpg==",
"license": "MIT",
"dependencies": {
- "@babel/compat-data": "^7.22.6",
- "@babel/helper-define-polyfill-provider": "^0.6.4",
+ "@babel/compat-data": "^7.27.7",
+ "@babel/helper-define-polyfill-provider": "^0.6.5",
"semver": "^6.3.1"
},
"peerDependencies": {
@@ -7103,12 +7109,12 @@
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
- "version": "0.6.4",
- "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.4.tgz",
- "integrity": "sha512-7gD3pRadPrbjhjLyxebmx/WrFYcuSjZ0XbdUujQMZ/fcE9oeewk2U/7PCvez84UeuK3oSjmPZ0Ch0dlupQvGzw==",
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.5.tgz",
+ "integrity": "sha512-ISqQ2frbiNU9vIJkzg7dlPpznPZ4jOiUQ1uSmB0fEHeowtN3COYRsXr/xexn64NpU13P06jc/L5TgiJXOgrbEg==",
"license": "MIT",
"dependencies": {
- "@babel/helper-define-polyfill-provider": "^0.6.4"
+ "@babel/helper-define-polyfill-provider": "^0.6.5"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
@@ -9022,9 +9028,9 @@
}
},
"node_modules/css-select": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
- "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
+ "integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
@@ -9051,9 +9057,9 @@
}
},
"node_modules/css-what": {
- "version": "6.1.0",
- "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
- "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
+ "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
@@ -9392,9 +9398,9 @@
}
},
"node_modules/dotenv": {
- "version": "16.5.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz",
- "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==",
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
@@ -9445,9 +9451,9 @@
"license": "MIT"
},
"node_modules/electron-to-chromium": {
- "version": "1.5.175",
- "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.175.tgz",
- "integrity": "sha512-Nqpef9mOVo7pZfl9NIUhj7tgtRTsMzCzRTJDP1ccim4Wb4YHOz3Le87uxeZq68OCNwau2iQ/X7UwdAZ3ReOkmg==",
+ "version": "1.5.178",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz",
+ "integrity": "sha512-wObbz/ar3Bc6e4X5vf0iO8xTN8YAjN/tgiAOJLr7yjYFtP9wAjq8Mb5h0yn6kResir+VYx2DXBj9NNobs0ETSA==",
"license": "ISC"
},
"node_modules/emittery": {
@@ -9951,9 +9957,9 @@
}
},
"node_modules/expo-camera": {
- "version": "16.1.8",
- "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.8.tgz",
- "integrity": "sha512-NpBbkUhHG6cs2TNUQBFSEtXb5j1/kTPIhiuqBcHosZG2yb/8MuM/ii4McJaqfe/6pn0YPqkH4k0Uod11DOSLmw==",
+ "version": "16.1.9",
+ "resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-16.1.9.tgz",
+ "integrity": "sha512-Aao0rNhhJxrWLx1moWZYBsRnEoZ04Vnm8uPO24V1161m5PLGptI7m5NS8NKoHx9McfrnDkvyJSWxz/7AhfEzTw==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
@@ -10005,12 +10011,12 @@
}
},
"node_modules/expo-dev-client": {
- "version": "5.2.1",
- "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.2.1.tgz",
- "integrity": "sha512-SzrHvXeyTGawzc/7ZIHFmaUYiCeRJagL9bJo/yTPmxdycFFOOdLs1FNMFXyYhB6YY4u5EKTCO6g1fug+0GV9sQ==",
+ "version": "5.2.2",
+ "resolved": "https://registry.npmjs.org/expo-dev-client/-/expo-dev-client-5.2.2.tgz",
+ "integrity": "sha512-shOMVwnfpq8ptpix+KveFz9UWv4SNxqxFYkEvRfsgyLiUG8O1vXE0FaZUNZgF+idOPGuO6C34YZM6pLzE0Dd6A==",
"license": "MIT",
"dependencies": {
- "expo-dev-launcher": "5.1.13",
+ "expo-dev-launcher": "5.1.14",
"expo-dev-menu": "6.1.12",
"expo-dev-menu-interface": "1.10.0",
"expo-manifests": "~0.16.5",
@@ -10021,9 +10027,9 @@
}
},
"node_modules/expo-dev-launcher": {
- "version": "5.1.13",
- "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.13.tgz",
- "integrity": "sha512-EAxkI0MOZk1E9tkk+QpyDhqlCjUqAr8q+mobcC3ZJIIi7KejhaQlGVlF1kUUITsYLvFvbT8egRgrsMO57T6uDA==",
+ "version": "5.1.14",
+ "resolved": "https://registry.npmjs.org/expo-dev-launcher/-/expo-dev-launcher-5.1.14.tgz",
+ "integrity": "sha512-pxXqn8xCgOuc+W32mRJ9DiVK7O0zmGTG/ePd3kw53Xg1PKB9iOeYNty53qZEkIWjt8xdqS0JdECQT4qjgA2d1g==",
"license": "MIT",
"dependencies": {
"ajv": "8.11.0",
@@ -10246,13 +10252,13 @@
}
},
"node_modules/expo-router": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.0.tgz",
- "integrity": "sha512-mnKpw35W6kKPpZm+ZxQei6HGUx2JO3znzqJZInzqrTZMgfAcHGgvP9AQFjg/Qi/Qy1CxunB9aQnqE9JPbSwbpw==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-5.1.1.tgz",
+ "integrity": "sha512-KYAp/SwkPVgY+8OI+UPGENZG4j+breoOMXmZ01s99U7X0dpSihKGSNpK6LkEoU31MXMLuUHGYYwD00zm9aqcSg==",
"license": "MIT",
"dependencies": {
"@expo/metro-runtime": "5.0.4",
- "@expo/server": "^0.6.2",
+ "@expo/server": "^0.6.3",
"@radix-ui/react-slot": "1.2.0",
"@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/native": "^7.1.6",
@@ -10751,39 +10757,39 @@
}
},
"node_modules/firebase": {
- "version": "11.9.1",
- "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.9.1.tgz",
- "integrity": "sha512-nbQbQxNlkHHRDn4cYwHdAKHwJPeZ0jRXxlNp6PCOb9CQx8Dc6Vjve97R34r1EZJnzOsPYZ3+ssJH7fkovDjvCw==",
+ "version": "11.10.0",
+ "resolved": "https://registry.npmjs.org/firebase/-/firebase-11.10.0.tgz",
+ "integrity": "sha512-nKBXoDzF0DrXTBQJlZa+sbC5By99ysYU1D6PkMRYknm0nCW7rJly47q492Ht7Ndz5MeYSBuboKuhS1e6mFC03w==",
"license": "Apache-2.0",
"dependencies": {
- "@firebase/ai": "1.4.0",
- "@firebase/analytics": "0.10.16",
- "@firebase/analytics-compat": "0.2.22",
- "@firebase/app": "0.13.1",
- "@firebase/app-check": "0.10.0",
- "@firebase/app-check-compat": "0.3.25",
- "@firebase/app-compat": "0.4.1",
+ "@firebase/ai": "1.4.1",
+ "@firebase/analytics": "0.10.17",
+ "@firebase/analytics-compat": "0.2.23",
+ "@firebase/app": "0.13.2",
+ "@firebase/app-check": "0.10.1",
+ "@firebase/app-check-compat": "0.3.26",
+ "@firebase/app-compat": "0.4.2",
"@firebase/app-types": "0.9.3",
- "@firebase/auth": "1.10.7",
- "@firebase/auth-compat": "0.5.27",
- "@firebase/data-connect": "0.3.9",
- "@firebase/database": "1.0.19",
- "@firebase/database-compat": "2.0.10",
- "@firebase/firestore": "4.7.17",
- "@firebase/firestore-compat": "0.3.52",
- "@firebase/functions": "0.12.8",
- "@firebase/functions-compat": "0.3.25",
- "@firebase/installations": "0.6.17",
- "@firebase/installations-compat": "0.2.17",
- "@firebase/messaging": "0.12.21",
- "@firebase/messaging-compat": "0.2.21",
- "@firebase/performance": "0.7.6",
- "@firebase/performance-compat": "0.2.19",
- "@firebase/remote-config": "0.6.4",
- "@firebase/remote-config-compat": "0.2.17",
- "@firebase/storage": "0.13.13",
- "@firebase/storage-compat": "0.3.23",
- "@firebase/util": "1.12.0"
+ "@firebase/auth": "1.10.8",
+ "@firebase/auth-compat": "0.5.28",
+ "@firebase/data-connect": "0.3.10",
+ "@firebase/database": "1.0.20",
+ "@firebase/database-compat": "2.0.11",
+ "@firebase/firestore": "4.8.0",
+ "@firebase/firestore-compat": "0.3.53",
+ "@firebase/functions": "0.12.9",
+ "@firebase/functions-compat": "0.3.26",
+ "@firebase/installations": "0.6.18",
+ "@firebase/installations-compat": "0.2.18",
+ "@firebase/messaging": "0.12.22",
+ "@firebase/messaging-compat": "0.2.22",
+ "@firebase/performance": "0.7.7",
+ "@firebase/performance-compat": "0.2.20",
+ "@firebase/remote-config": "0.6.5",
+ "@firebase/remote-config-compat": "0.2.18",
+ "@firebase/storage": "0.13.14",
+ "@firebase/storage-compat": "0.3.24",
+ "@firebase/util": "1.12.1"
}
},
"node_modules/flow-enums-runtime": {
@@ -14457,9 +14463,9 @@
}
},
"node_modules/jsdom/node_modules/ws": {
- "version": "8.18.2",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz",
- "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==",
+ "version": "8.18.3",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
+ "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"dev": true,
"license": "MIT",
"engines": {
@@ -16074,9 +16080,9 @@
}
},
"node_modules/napi-postinstall": {
- "version": "0.2.4",
- "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.4.tgz",
- "integrity": "sha512-ZEzHJwBhZ8qQSbknHqYcdtQVr8zUgGyM/q6h6qAyhtyVMNrSgDhrC4disf03dYW0e+czXyLnZINnCTEkWy0eJg==",
+ "version": "0.2.5",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.2.5.tgz",
+ "integrity": "sha512-kmsgUvCRIJohHjbZ3V8avP0I1Pekw329MVAMDzVxsrkjgdnqiwvMX5XwR+hWV66vsAtZ+iM+fVnq8RTQawUmCQ==",
"dev": true,
"license": "MIT",
"bin": {
@@ -17194,9 +17200,9 @@
}
},
"node_modules/react-devtools-core": {
- "version": "6.1.2",
- "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.2.tgz",
- "integrity": "sha512-ldFwzufLletzCikNJVYaxlxMLu7swJ3T2VrGfzXlMsVhZhPDKXA38DEROidaYZVgMAmQnIjymrmqto5pyfrwPA==",
+ "version": "6.1.3",
+ "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.3.tgz",
+ "integrity": "sha512-4be9IVco12d/4D7NpZgNjffbYIo/MAk4f5eBJR8PpKyiR7tgwe29liQbxyqDov5Ybc2crGABZyYAmdeU6NowKg==",
"license": "MIT",
"dependencies": {
"shell-quote": "^1.6.1",
@@ -19276,9 +19282,9 @@
}
},
"node_modules/tailwindcss": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.10.tgz",
- "integrity": "sha512-P3nr6WkvKV/ONsTzj6Gb57sWPMX29EPNPopo7+FcpkQaNsrNpZ1pv8QmrYI2RqEKD7mlGqLnGovlcYnBK0IqUA==",
+ "version": "4.1.11",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",
+ "integrity": "sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==",
"license": "MIT"
},
"node_modules/tapable": {
diff --git a/package.json b/package.json
index 526a237..8d05bd2 100644
--- a/package.json
+++ b/package.json
@@ -5,7 +5,8 @@
"private": true,
"scripts": {
"start": "expo start",
- "prebuild": "node scripts/setupBatteryOptimization.js && node scripts/notificationSounds.js",
+ "prebuild": "node scripts/setupExceptionHandler.js",
+ "eas-build-pre-install": "node scripts/setupExceptionHandler.js",
"android": "npm run prebuild && expo run:android",
"bump-version": "node scripts/bumpVersion.js",
"eas-build-preinstall": "node scripts/setupBatteryOptimization.js && node scripts/notificationSounds.js",
@@ -24,6 +25,7 @@
"@react-native-firebase/app": "^22.2.1",
"@react-native-firebase/auth": "^22.2.1",
"@react-native-firebase/messaging": "^22.2.1",
+ "@react-native-google-signin/google-signin": "^15.0.0",
"@react-navigation/native": "^7.1.6",
"@react-navigation/native-stack": "^7.3.10",
"babel-preset-expo": "~13.0.0",
@@ -94,4 +96,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
new file mode 100644
index 0000000..281936b
--- /dev/null
+++ b/pnpm-workspace.yaml
@@ -0,0 +1,5 @@
+packages:
+ # all packages in subdirs of packages/
+ - 'packages/*'
+ # if you have other directories, add them here
+ # - 'apps/*'
diff --git a/providers/AuthProvider.tsx b/providers/AuthProvider.tsx
new file mode 100644
index 0000000..8d6bea1
--- /dev/null
+++ b/providers/AuthProvider.tsx
@@ -0,0 +1,15 @@
+// providers/AuthProvider.tsx
+import { createContext, useState, type ReactNode } from 'react';
+import type { AuthUser, AuthContextType } from '../hooks/useAuth';
+
+export const AuthContext = createContext(undefined);
+
+export function AuthProvider({ children }: { children: ReactNode }) {
+ const [user, setUser] = useState(null);
+
+ return (
+
+ {children}
+
+ );
+}
diff --git a/providers/NotificationProvider.tsx b/providers/NotificationProvider.tsx
new file mode 100644
index 0000000..048c971
--- /dev/null
+++ b/providers/NotificationProvider.tsx
@@ -0,0 +1,144 @@
+import React, { useEffect, useMemo, useRef, useState } from 'react';
+import { View, Text, StyleSheet, Pressable, Animated } from 'react-native';
+import { subscribe } from '@/services/notifier.service';
+
+export type BannerItem = {
+ id: string;
+ type: 'info' | 'success' | 'warning' | 'error';
+ title: string;
+ message: string;
+ duration: number;
+};
+
+const maxStack = 4;
+
+function useAnimatedEntrance() {
+ const translateY = useRef(new Animated.Value(-40)).current;
+ const opacity = useRef(new Animated.Value(0)).current;
+ useEffect(() => {
+ Animated.parallel([
+ Animated.timing(translateY, { toValue: 0, duration: 200, useNativeDriver: true }),
+ Animated.timing(opacity, { toValue: 1, duration: 200, useNativeDriver: true }),
+ ]).start();
+ }, [translateY, opacity]);
+ return { translateY, opacity };
+}
+
+function Banner({ item, onClose }: { item: BannerItem; onClose: (id: string) => void }) {
+ const { translateY, opacity } = useAnimatedEntrance();
+ const color = item.type === 'success' ? '#1b5e20' : item.type === 'warning' ? '#9a6b00' : item.type === 'info' ? '#0d47a1' : '#7f0000';
+
+ return (
+
+
+ {item.title}
+
+ {item.message}
+
+
+ onClose(item.id)}>
+ Γ
+
+
+ );
+}
+
+export function NotificationProvider({ children }: { children: React.ReactNode }) {
+ const [items, setItems] = useState([]);
+ const timers = useRef(new Map>());
+
+ useEffect(() => {
+ const unsub = subscribe((n) => {
+ setItems((prev) => {
+ const next = [...prev, n].slice(-maxStack);
+ return next;
+ });
+ // auto-dismiss
+ if (timers.current.has(n.id)) clearTimeout(timers.current.get(n.id)!);
+ timers.current.set(
+ n.id,
+ setTimeout(() => {
+ setItems((curr) => curr.filter((x) => x.id !== n.id));
+ timers.current.delete(n.id);
+ }, n.duration)
+ );
+ });
+ return () => {
+ unsub();
+ // clear timers
+ timers.current.forEach((t) => clearTimeout(t));
+ timers.current.clear();
+ };
+ }, []);
+
+ const onClose = (id: string) => {
+ if (timers.current.has(id)) {
+ clearTimeout(timers.current.get(id)!);
+ timers.current.delete(id);
+ }
+ setItems((curr) => curr.filter((x) => x.id !== id));
+ };
+
+ const stack = useMemo(
+ () => (
+
+
+ {items.map((it) => (
+
+ ))}
+
+
+ ),
+ [items]
+ );
+
+ return (
+
+ {children}
+ {stack}
+
+ );
+}
+
+const styles = StyleSheet.create({
+ container: {
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ zIndex: 9999,
+ // allow touches to pass through except on banners
+ pointerEvents: 'box-none',
+ },
+ stack: {
+ marginTop: 40,
+ paddingHorizontal: 12,
+ gap: 8,
+ },
+ banner: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ paddingHorizontal: 12,
+ paddingVertical: 10,
+ borderRadius: 8,
+ shadowColor: '#000',
+ shadowOpacity: 0.2,
+ shadowRadius: 6,
+ shadowOffset: { width: 0, height: 2 },
+ elevation: 2,
+ },
+ bannerTitle: {
+ fontWeight: '700',
+ color: 'white',
+ marginBottom: 2,
+ },
+ bannerText: {
+ color: 'white',
+ },
+ close: {
+ color: 'white',
+ fontSize: 22,
+ marginLeft: 10,
+ lineHeight: 22,
+ },
+});
diff --git a/scripts/build-config.json b/scripts/build-config.json
new file mode 100644
index 0000000..01ea4be
--- /dev/null
+++ b/scripts/build-config.json
@@ -0,0 +1,7 @@
+{
+ "scripts": [
+ "setupBatteryOptimizations.js",
+ "notificationSounds.ts",
+ "setupExceptionHandler.js"
+ ]
+}
\ No newline at end of file
diff --git a/scripts/buildScripts.js b/scripts/buildScripts.js
new file mode 100644
index 0000000..aacbb1b
--- /dev/null
+++ b/scripts/buildScripts.js
@@ -0,0 +1,80 @@
+const fs = require("fs");
+const path = require("path");
+const { execSync, execFileSync } = require("child_process");
+
+// Configuration file path
+const CONFIG_FILE = path.join(__dirname, "build-config.json");
+
+// Default configuration
+const DEFAULT_CONFIG = {
+ scripts: [
+ "setupBatteryOptimizations.js",
+ "notificationSounds.ts",
+ "setupExceptionHandler.js",
+ ],
+ // Add enabled: false to disable a script without removing it from the list
+ // Example: { path: "scriptName.js", enabled: false }
+};
+
+// Create default config if it doesn't exist
+function ensureConfigExists() {
+ if (!fs.existsSync(CONFIG_FILE)) {
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(DEFAULT_CONFIG, null, 2));
+ console.log(`Created default build config at ${CONFIG_FILE}`);
+ }
+}
+
+// Get the list of scripts to run
+function getScriptsToRun() {
+ ensureConfigExists();
+ const config = JSON.parse(fs.readFileSync(CONFIG_FILE, "utf8"));
+ return config.scripts
+ .filter((script) => {
+ if (typeof script === "string") return true;
+ return script.enabled !== false;
+ })
+ .map((script) => {
+ if (typeof script === "string") return script;
+ return script.path;
+ });
+}
+
+// Run all enabled scripts
+function runBuildScripts() {
+ console.log("π Running build scripts...");
+
+ const scripts = getScriptsToRun();
+
+ scripts.forEach((scriptPath) => {
+ const fullPath = path.join(__dirname, scriptPath);
+
+ if (!fs.existsSync(fullPath)) {
+ console.error(`β Script not found: ${fullPath}`);
+ return;
+ }
+
+ console.log(`βΆοΈ Running: ${scriptPath}`);
+
+ try {
+ if (scriptPath.endsWith(".ts")) {
+ // For TypeScript files, use ts-node
+ execFileSync("npx", ["ts-node", fullPath], { stdio: "inherit" });
+ } else {
+ // For JavaScript files, use node
+ execFileSync("node", [fullPath], { stdio: "inherit" });
+ }
+ console.log(`β
Completed: ${scriptPath}`);
+ } catch (error) {
+ console.error(`β Error running ${scriptPath}:`, error.message);
+ }
+ });
+
+ console.log("β¨ Build scripts completed");
+}
+
+// Run the function if this is the main module
+if (require.main === module) {
+ runBuildScripts();
+}
+
+module.exports = runBuildScripts;
diff --git a/scripts/setupExceptionHandler.js b/scripts/setupExceptionHandler.js
new file mode 100644
index 0000000..51d743c
--- /dev/null
+++ b/scripts/setupExceptionHandler.js
@@ -0,0 +1,211 @@
+const fs = require("fs");
+const path = require("path");
+const { execSync } = require("child_process");
+
+// Get the app package name
+function getPackageName() {
+ try {
+ const appJson = JSON.parse(fs.readFileSync("./app.json", "utf8"));
+ return (
+ appJson.expo.android?.package || "com.codebuilderinc.codebuilderapps"
+ );
+ } catch (error) {
+ console.error("Error getting package name:", error);
+ return "com.codebuilderinc.codebuilderapps"; // Fallback
+ }
+}
+
+// Setup the TS global handler that works with your actual error service
+function setupTSErrorHandler() {
+ console.log("π± Setting up TypeScript global error handler...");
+
+ const utilsDir = path.join("src", "utils");
+ const handlerPath = path.join(utilsDir, "errorHandler.ts");
+
+ // Create utils directory if it doesn't exist
+ if (!fs.existsSync(utilsDir)) {
+ fs.mkdirSync(utilsDir, { recursive: true });
+ }
+
+ // Content for the error handler that uses your actual error reporting service
+ const handlerContent = `
+import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
+import { Alert } from 'react-native';
+import { errorReportingService } from '../services/errorReporting.service';
+
+// JavaScript Error Handler
+export const setupGlobalErrorHandlers = () => {
+ setJSExceptionHandler((error, isFatal) => {
+ // Log the error to your existing error reporting service
+ errorReportingService.submitError?.(error) ||
+ errorReportingService.reportError?.(error) ||
+ console.error('Caught JS Exception:', error);
+
+ // Show a friendly message instead of crashing
+ Alert.alert(
+ 'Unexpected Error Occurred',
+ 'We encountered an issue. The app will continue running, and our team has been notified.',
+ [{ text: 'OK' }]
+ );
+ }, true);
+
+ // Native Exception Handler
+ setNativeExceptionHandler(
+ (exceptionString) => {
+ // Handle native exceptions
+ const error = new Error(\`Native Exception: \${exceptionString}\`);
+ errorReportingService.submitError?.(error) ||
+ errorReportingService.reportError?.(error) ||
+ console.error('Caught Native Exception:', exceptionString);
+ },
+ false, // don't force app to quit
+ true // should catch all exceptions
+ );
+};
+`;
+
+ // Write the file
+ fs.writeFileSync(handlerPath, handlerContent);
+ console.log(`β
Created error handler at ${handlerPath}`);
+}
+
+// Setup the Android native exception handler
+function setupNativeExceptionHandler() {
+ console.log("π± Setting up Android native exception handler...");
+
+ // Get package name and calculate path
+ const packageName = getPackageName();
+ const packagePath = packageName.replace(/\./g, "/");
+
+ // Path to MainActivity.java
+ const mainActivityPath = path.join(
+ "android",
+ "app",
+ "src",
+ "main",
+ "java",
+ packagePath,
+ "MainActivity.java"
+ );
+
+ // Check if file exists
+ if (!fs.existsSync(mainActivityPath)) {
+ console.log(
+ `β MainActivity.java not found at ${mainActivityPath}. Run 'npx expo prebuild' first.`
+ );
+ return;
+ }
+
+ // Read the file
+ let mainActivityContent = fs.readFileSync(mainActivityPath, "utf8");
+
+ // Check if we've already modified this file
+ if (mainActivityContent.includes("// CUSTOM EXCEPTION HANDLER")) {
+ console.log("β
Exception handler already set up in MainActivity.java");
+ return;
+ }
+
+ // Find the onCreate method or prepare to add it
+ const onCreateRegex =
+ /protected void onCreate\s*\(\s*Bundle savedInstanceState\s*\)\s*\{/;
+ const hasOnCreate = onCreateRegex.test(mainActivityContent);
+
+ let exceptionHandlerCode;
+
+ if (hasOnCreate) {
+ // Append to existing onCreate method
+ exceptionHandlerCode = `
+ // CUSTOM EXCEPTION HANDLER
+ final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+ Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ // Log the exception but don't crash the app
+ System.err.println("Caught unhandled exception: " + throwable.getMessage());
+ throwable.printStackTrace();
+
+ // Don't call the default handler to prevent crash screen
+ // defaultHandler.uncaughtException(thread, throwable);
+ }
+ });`;
+
+ // Insert the code after the opening brace of onCreate
+ mainActivityContent = mainActivityContent.replace(
+ onCreateRegex,
+ `protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);${exceptionHandlerCode}`
+ );
+ } else {
+ // Need to add the entire onCreate method
+ exceptionHandlerCode = `
+ // CUSTOM EXCEPTION HANDLER
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
+ Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
+ @Override
+ public void uncaughtException(Thread thread, Throwable throwable) {
+ // Log the exception but don't crash the app
+ System.err.println("Caught unhandled exception: " + throwable.getMessage());
+ throwable.printStackTrace();
+
+ // Don't call the default handler to prevent crash screen
+ // defaultHandler.uncaughtException(thread, throwable);
+ }
+ });
+ }
+`;
+
+ // Add the import for Bundle if needed
+ if (!mainActivityContent.includes("import android.os.Bundle;")) {
+ mainActivityContent = mainActivityContent.replace(
+ "import com.facebook.react.ReactActivity;",
+ "import com.facebook.react.ReactActivity;\nimport android.os.Bundle;"
+ );
+ }
+
+ // Find the closing brace of the class and insert before it
+ const lastBraceIndex = mainActivityContent.lastIndexOf("}");
+ mainActivityContent =
+ mainActivityContent.substring(0, lastBraceIndex) +
+ exceptionHandlerCode +
+ mainActivityContent.substring(lastBraceIndex);
+ }
+
+ // Write the modified file
+ fs.writeFileSync(mainActivityPath, mainActivityContent);
+ console.log("β
Exception handler successfully added to MainActivity.java");
+}
+
+// Run both setup functions
+function setupExceptionHandlers() {
+ // Setup the JS error handler utility
+ setupTSErrorHandler();
+
+ // Setup the native Android exception handler
+ setupNativeExceptionHandler();
+
+ // Reminder for necessary package
+ console.log(`
+π Next steps:
+1. Add 'react-native-exception-handler' to your dependencies:
+ npm install react-native-exception-handler
+
+2. Import and use the handler in your _layout.tsx:
+ import { setupGlobalErrorHandlers } from '../src/utils/errorHandler';
+
+ // Add inside a useEffect:
+ useEffect(() => {
+ setupGlobalErrorHandlers();
+ }, []);
+ `);
+}
+
+// Run the function if this is the main module
+if (require.main === module) {
+ setupExceptionHandlers();
+}
+
+module.exports = setupExceptionHandlers;
diff --git a/services/notifier.service.ts b/services/notifier.service.ts
new file mode 100644
index 0000000..481ecbe
--- /dev/null
+++ b/services/notifier.service.ts
@@ -0,0 +1,62 @@
+// A minimal pub/sub notifier for in-app banners (non-blocking)
+// Allows triggering notifications from anywhere (including non-React files)
+
+export type NotifierType = 'info' | 'success' | 'warning' | 'error';
+
+export type NotifyPayload = {
+ id?: string;
+ type?: NotifierType;
+ title?: string;
+ message: string;
+ duration?: number; // ms
+};
+
+type InternalNotify = Required> & { id: string };
+
+type Subscriber = (n: InternalNotify) => void;
+
+const subscribers = new Set();
+const defaultDuration = 5000;
+const recentMap = new Map();
+const dedupeWindowMs = 2000; // ignore duplicates in this window
+
+export function subscribe(sub: Subscriber) {
+ subscribers.add(sub);
+ return () => subscribers.delete(sub);
+}
+
+export function notify(payload: NotifyPayload): string | null {
+ const id = payload.id || `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
+ const type = payload.type ?? 'error';
+ const title = payload.title ?? (type === 'error' ? 'Unexpected Error' : 'Notice');
+ const message = payload.message;
+ const duration = payload.duration ?? defaultDuration;
+
+ if (!message) return null;
+
+ const key = `${type}|${title}|${message}`;
+ const now = Date.now();
+ const last = recentMap.get(key) ?? 0;
+ if (now - last < dedupeWindowMs) return null; // drop duplicate spam
+ recentMap.set(key, now);
+
+ const data: InternalNotify = { id, type, title, message, duration };
+ subscribers.forEach((s) => s(data));
+ return id;
+}
+
+export function notifyError(err: unknown, fallback = 'An unexpected error occurred.') {
+ const message = normalizeError(err) || fallback;
+ return notify({ type: 'error', title: 'Unexpected Error', message });
+}
+
+function normalizeError(err: unknown): string | null {
+ if (!err) return null;
+ if (err instanceof Error) return err.message || String(err);
+ if (typeof err === 'string') return err;
+ try {
+ return JSON.stringify(err);
+ } catch {
+ return String(err);
+ }
+}
diff --git a/utils/globalErrorhandler.ts b/utils/globalErrorhandler.ts
new file mode 100644
index 0000000..f35e269
--- /dev/null
+++ b/utils/globalErrorhandler.ts
@@ -0,0 +1,25 @@
+import { setJSExceptionHandler, setNativeExceptionHandler } from 'react-native-exception-handler';
+import { reportError } from '../services/errorReporting.service';
+import { notifyError } from '@/services/notifier.service';
+
+// JavaScript Error Handler
+export const setupGlobalErrorHandlers = () => {
+ setJSExceptionHandler((error, isFatal) => {
+ // Report the error using your actual reportError function
+ reportError(error, { isFatal });
+
+ // Show a non-blocking stacked banner instead of a modal alert
+ notifyError(error, 'We encountered an issue. The app will continue running, and our team has been notified.');
+ }, true);
+
+ // Native Exception Handler
+ setNativeExceptionHandler(
+ (exceptionString) => {
+ // This is called when native code throws an exception
+ const error = new Error(`Native Exception: ${exceptionString}`);
+ reportError(error, { isFatal: true });
+ },
+ false, // don't force app to quit
+ true // should catch all exceptions
+ );
+};
diff --git a/utils/tasks.utils.ts b/utils/tasks.utils.ts
index 1c46ef0..0e077d4 100644
--- a/utils/tasks.utils.ts
+++ b/utils/tasks.utils.ts
@@ -1,39 +1,52 @@
-import * as TaskManager from "expo-task-manager";
-import * as BackgroundFetch from "expo-background-fetch";
+import * as TaskManager from 'expo-task-manager';
+import * as BackgroundFetch from 'expo-background-fetch';
-const BACKGROUND_FETCH_TASK = "background-fetch-task";
+const BACKGROUND_FETCH_TASK = 'background-fetch-task';
// Define the task
TaskManager.defineTask(BACKGROUND_FETCH_TASK, async () => {
- try {
- console.log("Background fetch task started");
- // Perform your API request
- const response = await fetch(
- "https://new.codebuilder.org/api/reddit/posts"
- );
- const data = await response.json();
+ try {
+ console.log('----------------------- Background fetch task started ---------------------');
+ // Perform your API request
+ const response = await fetch('https://new.codebuilder.org/api/jobs');
+ const contentType = response.headers.get('content-type') || '';
- // Handle the fetched data
- console.log("Fetched data:", data);
+ if (!response.ok) {
+ const text = await response.text();
+ console.error('Background fetch HTTP error', response.status, text.slice(0, 200));
+ return BackgroundFetch.BackgroundFetchResult.Failed;
+ }
- return BackgroundFetch.BackgroundFetchResult.NewData; // Task succeeded
- } catch (error) {
- console.error("Background fetch failed:", error);
- return BackgroundFetch.BackgroundFetchResult.Failed; // Task failed
- }
+ let data: unknown;
+ if (contentType.includes('application/json')) {
+ data = await response.json();
+ } else {
+ const text = await response.text();
+ console.warn('Background fetch received non-JSON response:', text.slice(0, 200));
+ data = text;
+ }
+
+ // Handle the fetched data
+ console.log('Fetched data:', data);
+
+ return BackgroundFetch.BackgroundFetchResult.NewData; // Task succeeded
+ } catch (error) {
+ console.error('Background fetch failed:', error);
+ return BackgroundFetch.BackgroundFetchResult.Failed; // Task failed
+ }
});
// Register the task
export async function registerBackgroundFetch() {
- const status = await BackgroundFetch.getStatusAsync();
- if (status === BackgroundFetch.BackgroundFetchStatus.Available) {
- await BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
- minimumInterval: 60, // Fetch interval in seconds (not guaranteed to be exact)
- stopOnTerminate: false, // Continue task when app is closed
- startOnBoot: true, // Start task when device is rebooted
- });
- console.log("Background fetch task registered");
- } else {
- console.error("Background fetch is not available");
- }
+ const status = await BackgroundFetch.getStatusAsync();
+ if (status === BackgroundFetch.BackgroundFetchStatus.Available) {
+ await BackgroundFetch.registerTaskAsync(BACKGROUND_FETCH_TASK, {
+ minimumInterval: 60, // Fetch interval in seconds (not guaranteed to be exact)
+ stopOnTerminate: false, // Continue task when app is closed
+ startOnBoot: true, // Start task when device is rebooted
+ });
+ console.log('Background fetch task registered');
+ } else {
+ console.error('Background fetch is not available');
+ }
}