feat: iphone app
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>API_KEY</key>
|
||||
<string>AIzaSyCcoyvOKXmZisfcjiEYkNl3o5-_5ajiiN4</string>
|
||||
<key>GCM_SENDER_ID</key>
|
||||
<string>167940133580</string>
|
||||
<key>PLIST_VERSION</key>
|
||||
<string>1</string>
|
||||
<key>BUNDLE_ID</key>
|
||||
<string>com.haptika.mobile.dev</string>
|
||||
<key>PROJECT_ID</key>
|
||||
<string>haptika-d13d3</string>
|
||||
<key>STORAGE_BUCKET</key>
|
||||
<string>haptika-d13d3.firebasestorage.app</string>
|
||||
<key>IS_ADS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_ANALYTICS_ENABLED</key>
|
||||
<false></false>
|
||||
<key>IS_APPINVITE_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_GCM_ENABLED</key>
|
||||
<true></true>
|
||||
<key>IS_SIGNIN_ENABLED</key>
|
||||
<true></true>
|
||||
<key>GOOGLE_APP_ID</key>
|
||||
<string>1:167940133580:ios:0dfa5b8ff3f4b682896869</string>
|
||||
</dict>
|
||||
</plist>
|
||||
+7
-1
@@ -41,7 +41,13 @@ Le choix iPhone / iPhone + Apple Watch se fait sur l'étape « Comment allez vou
|
||||
|
||||
## Limites fonctionnelles
|
||||
|
||||
Cette livraison implémente le front du Figma. Les valeurs biométriques, profils, historiques et états « Connecté » affichés sont ceux de la maquette ; ils ne constituent pas des mesures ou connexions réelles. Le front n'est connecté ni à Firebase, ni à Apple Santé, ni à la Watch. Les mesures caméra, protocoles thérapeutiques, alarmes système, authentification et achats ne sont pas implémentés dans ce nouveau projet. Le code natif existant du dépôt parent n'a pas été transféré ni modifié.
|
||||
Le SDK natif Firebase est configuré sur `haptika-d13d3`. L'écran `haptika-mobile://account`, accessible en touchant « COMPTE ET ABONNEMENT » dans Profil, permet la création de compte, la connexion e-mail/mot de passe, la réinitialisation du mot de passe et la déconnexion. Cet écran technique de compte est un ajout fonctionnel, pas une frame Figma.
|
||||
|
||||
Le prénom, l'année de naissance valide, le genre, le choix téléphone/Watch, la fin d'onboarding et le mode écran noir se synchronisent après connexion. Les autres curseurs restent locaux. Les changements non envoyés sont conservés dans une file locale séparée par UID ; ils repartent au retour du réseau (réessai toutes les 10 secondes) ou avec « Réessayer ». Les données du mode local ne sont pas importées à la connexion.
|
||||
|
||||
Les valeurs biométriques, historiques, abonnements et états « Connecté » des frames restent des exemples ; aucune de ces valeurs n'est envoyée à Firestore. Apple Santé, l'acquisition caméra, WatchConnectivity dans Expo, les alarmes système et les achats ne sont pas branchés. Les repositories de mesures et de séances sont disponibles pour leur futur branchement. Le code natif de l'ancienne application Watch reste distinct.
|
||||
|
||||
Les règles et index Firestore sont déployés. Les fonctions serveur de suppression sont écrites et testées sur Emulator ; leur déploiement nécessite Blaze, actuellement désactivé. Voir `../firebase/README.md`.
|
||||
|
||||
Les destinations de profil sans frame correspondante (abonnement, support, etc.) n'ouvrent pas d'écran inventé. Les panneaux explicatifs définis dans Figma sont associés aux boutons d'information disponibles.
|
||||
|
||||
|
||||
+39
-5
@@ -7,9 +7,43 @@
|
||||
"orientation": "portrait",
|
||||
"userInterfaceStyle": "dark",
|
||||
"newArchEnabled": true,
|
||||
"ios": { "supportsTablet": false, "bundleIdentifier": "com.haptika.mobile.dev" },
|
||||
"android": { "package": "com.haptika.mobile.dev", "edgeToEdgeEnabled": true },
|
||||
"plugins": ["expo-router", "expo-font", ["expo-dev-client", {"launchMode":"most-recent"}]],
|
||||
"experiments": {"typedRoutes": true}
|
||||
"ios": {
|
||||
"supportsTablet": false,
|
||||
"bundleIdentifier": "com.haptika.mobile.dev",
|
||||
"googleServicesFile": "./GoogleService-Info.plist"
|
||||
},
|
||||
"android": {
|
||||
"package": "com.haptika.mobile.dev",
|
||||
"edgeToEdgeEnabled": true,
|
||||
"googleServicesFile": "./google-services.json"
|
||||
},
|
||||
"plugins": [
|
||||
"expo-router",
|
||||
"expo-font",
|
||||
[
|
||||
"expo-dev-client",
|
||||
{
|
||||
"launchMode": "most-recent"
|
||||
}
|
||||
],
|
||||
"@react-native-firebase/app",
|
||||
"@react-native-firebase/auth",
|
||||
[
|
||||
"expo-build-properties",
|
||||
{
|
||||
"ios": {
|
||||
"useFrameworks": "static",
|
||||
"forceStaticLinking": [
|
||||
"RNFBApp",
|
||||
"RNFBAuth",
|
||||
"RNFBFirestore"
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"experiments": {
|
||||
"typedRoutes": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,11 +2,12 @@ import { Stack } from 'expo-router';
|
||||
import { StatusBar } from 'expo-status-bar';
|
||||
import { useFonts } from 'expo-font';
|
||||
import { SafeAreaProvider } from 'react-native-safe-area-context';
|
||||
import {AuthProvider} from '../src/firebase/auth-state';
|
||||
import { AppProvider } from '../src/state/app-state';
|
||||
import { theme } from '../src/design/theme';
|
||||
|
||||
export default function Layout() {
|
||||
const [loaded, error] = useFonts({Inter: require('../assets/fonts/Inter.ttf')});
|
||||
if (!loaded && !error) return null;
|
||||
return <SafeAreaProvider><AppProvider><StatusBar style="light" /><Stack screenOptions={{headerShown: false, contentStyle: {backgroundColor: theme.background}, animation: 'fade', gestureEnabled: true}} /></AppProvider></SafeAreaProvider>;
|
||||
return <SafeAreaProvider><AuthProvider><AppProvider><StatusBar style="light" /><Stack screenOptions={{headerShown: false, contentStyle: {backgroundColor: theme.background}, animation: 'fade', gestureEnabled: true}} /></AppProvider></AuthProvider></SafeAreaProvider>;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import React,{useState} from 'react';
|
||||
import {Alert,ActivityIndicator,Pressable,ScrollView,Text,TextInput,View} from 'react-native';
|
||||
import {useRouter} from 'expo-router';
|
||||
import {SafeAreaView} from 'react-native-safe-area-context';
|
||||
import {createUserWithEmailAndPassword,signInWithEmailAndPassword,sendPasswordResetEmail,signOut} from '@react-native-firebase/auth';
|
||||
import {auth} from '../src/firebase/client';
|
||||
import {useAuth} from '../src/firebase/auth-state';
|
||||
import {useAppState} from '../src/state/app-state';
|
||||
import {theme} from '../src/design/theme';
|
||||
|
||||
export default function AccountScreen(){
|
||||
const router=useRouter();const {user}=useAuth();const {syncStatus,syncError,retry}=useAppState();
|
||||
const [email,setEmail]=useState(''),[password,setPassword]=useState(''),[register,setRegister]=useState(false),[busy,setBusy]=useState(false),[error,setError]=useState<string|null>(null);
|
||||
async function submit(){setBusy(true);setError(null);try{if(register)await createUserWithEmailAndPassword(auth,email.trim(),password);else await signInWithEmailAndPassword(auth,email.trim(),password);setPassword('');}catch(e){const code=(e as {code?:string}).code;setError(code==='auth/network-request-failed'?'Connexion indisponible. Réessaie lorsque le réseau est disponible.':code==='auth/weak-password'?'Choisis un mot de passe d’au moins 6 caractères.':code==='auth/email-already-in-use'?'Un compte utilise déjà cette adresse.':'Connexion impossible. Vérifie ton adresse et ton mot de passe.');}finally{setBusy(false);}}
|
||||
const button=(title:string,action:()=>void)=> <Pressable accessibilityRole="button" disabled={busy} onPress={action} style={{backgroundColor:theme.accent,borderRadius:28,padding:16,marginTop:16}}><Text style={{color:'#06101d',fontSize:16,textAlign:'center',fontWeight:'600'}}>{title}</Text></Pressable>;
|
||||
return <SafeAreaView style={{flex:1,backgroundColor:theme.background}}><ScrollView keyboardShouldPersistTaps="handled" contentContainerStyle={{padding:24,gap:18}}><Pressable accessibilityRole="button" onPress={()=>router.canGoBack()?router.back():router.replace('/')}><Text style={{color:theme.accent}}>Retour</Text></Pressable><Text style={{fontSize:28,color:theme.text,fontWeight:'600'}}>Mon compte</Text>
|
||||
{user?<><Text selectable style={{color:theme.text}}>{user.email}</Text><Text accessibilityLiveRegion="polite" style={{color:theme.muted}}>{syncStatus==='synced'?'Profil synchronisé':syncStatus==='pending'?'Modifications en attente':syncStatus==='loading'?'Chargement…':'Synchronisation indisponible'}</Text>{syncError&&<Text style={{color:'#ffb4ab'}}>{syncError}</Text>}{syncStatus==='error'&&button('Réessayer',retry)}{button('Se déconnecter',()=>{if(syncStatus==='pending'||syncStatus==='error'){Alert.alert('Modifications en attente','Elles resteront sur cet appareil, liées à ce compte.',[{text:'Annuler',style:'cancel'},{text:'Se déconnecter',onPress:()=>void signOut(auth).catch(()=>setError('Déconnexion impossible.'))}]);}else void signOut(auth).catch(()=>setError('Déconnexion impossible.'));})}</>:<><Text style={{color:theme.muted}}>Connecte-toi pour retrouver ton profil et tes préférences. Les données du mode local restent sur cet appareil.</Text><TextInput accessibilityLabel="Adresse e-mail" placeholder="Adresse e-mail" placeholderTextColor={theme.muted} autoCapitalize="none" autoCorrect={false} keyboardType="email-address" textContentType="emailAddress" value={email} onChangeText={setEmail} style={{color:theme.text,borderWidth:1,borderColor:theme.muted,borderRadius:14,padding:16}}/><TextInput accessibilityLabel="Mot de passe" placeholder="Mot de passe" placeholderTextColor={theme.muted} secureTextEntry textContentType={register?'newPassword':'password'} value={password} onChangeText={setPassword} style={{color:theme.text,borderWidth:1,borderColor:theme.muted,borderRadius:14,padding:16}}/>{button(register?'Créer mon compte':'Se connecter',()=>void submit())}<Pressable disabled={busy} onPress={()=>{setRegister(!register);setError(null);}}><Text style={{color:theme.accent}}>{register?'J’ai déjà un compte':'Créer un compte'}</Text></Pressable><Pressable disabled={busy} onPress={()=>{if(!email.trim()){setError('Renseigne ton adresse e-mail.');return;}setBusy(true);void sendPasswordResetEmail(auth,email.trim()).then(()=>Alert.alert('Demande envoyée','Si un compte correspond à cette adresse, tu recevras un lien de réinitialisation.')).catch(()=>setError('Impossible d’envoyer la demande. Vérifie ton adresse et réessaie.')).finally(()=>setBusy(false));}}><Text style={{color:theme.accent}}>Mot de passe oublié</Text></Pressable></>}
|
||||
{busy&&<ActivityIndicator color={theme.accent}/>}{error&&<Text accessibilityRole="alert" style={{color:'#ffb4ab'}}>{error}</Text>}
|
||||
</ScrollView></SafeAreaView>;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Redirect } from 'expo-router';
|
||||
import { useAppState } from '../src/state/app-state';
|
||||
export default function Index() {
|
||||
const {state, ready} = useAppState();
|
||||
if (!ready) return null;
|
||||
const {state, ready, syncStatus} = useAppState();
|
||||
if (!ready || syncStatus === 'loading') return null;
|
||||
return <Redirect href={{pathname: '/screen/[id]', params: {id: state.onboarded ? (state.variant === 'watch' ? '560-251' : '431-2') : '2-2'}}} />;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"project_info": {
|
||||
"project_number": "167940133580",
|
||||
"project_id": "haptika-d13d3",
|
||||
"storage_bucket": "haptika-d13d3.firebasestorage.app"
|
||||
},
|
||||
"client": [
|
||||
{
|
||||
"client_info": {
|
||||
"mobilesdk_app_id": "1:167940133580:android:e5308e6482d55752896869",
|
||||
"android_client_info": {
|
||||
"package_name": "com.haptika.mobile.dev"
|
||||
}
|
||||
},
|
||||
"oauth_client": [],
|
||||
"api_key": [
|
||||
{
|
||||
"current_key": "AIzaSyC8SHKvK24Pjom5dUbBzGePrHilRXx-O48"
|
||||
}
|
||||
],
|
||||
"services": {
|
||||
"appinvite_service": {
|
||||
"other_platform_oauth_client": []
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"configuration_version": "1"
|
||||
}
|
||||
Generated
+898
@@ -10,7 +10,11 @@
|
||||
"dependencies": {
|
||||
"@expo/metro-runtime": "~6.1.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-firebase/app": "23.8.8",
|
||||
"@react-native-firebase/auth": "23.8.8",
|
||||
"@react-native-firebase/firestore": "23.8.8",
|
||||
"expo": "~54.0.36",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
"expo-constants": "~18.0.14",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-font": "~14.0.12",
|
||||
@@ -2437,6 +2441,645 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/ai": {
|
||||
"version": "2.9.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/ai/-/ai-2.9.0.tgz",
|
||||
"integrity": "sha512-NPvBBuvdGo9x3esnABAucFYmqbBmXvyTMimBq2PCuLZbdANZoHzGlx7vfzbwNDaEtCBq4RGGNMliLIv6bZ+PtA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app-check-interop-types": "0.3.3",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x",
|
||||
"@firebase/app-types": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/analytics": {
|
||||
"version": "0.10.20",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/analytics/-/analytics-0.10.20.tgz",
|
||||
"integrity": "sha512-adGTNVUWH5q66tI/OQuKLSN6mamPpfYhj0radlH2xt+3eL6NFPtXoOs+ulvs+UsmK27vNFx5FjRDfWk+TyduHg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/analytics-compat": {
|
||||
"version": "0.2.26",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/analytics-compat/-/analytics-compat-0.2.26.tgz",
|
||||
"integrity": "sha512-0j2ruLOoVSwwcXAF53AMoniJKnkwiTjGVfic5LDzqiRkR13vb5j6TXMeix787zbLeQtN/m1883Yv1TxI0gItbA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/analytics": "0.10.20",
|
||||
"@firebase/analytics-types": "0.8.3",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/analytics-types": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/analytics-types/-/analytics-types-0.8.3.tgz",
|
||||
"integrity": "sha512-VrIp/d8iq2g501qO46uGz3hjbDb8xzYMrbu8Tp0ovzIzrvJZ2fvmj649gTjge/b7cCCcjT0H37g1gVtlNhnkbg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/app": {
|
||||
"version": "0.14.9",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app/-/app-0.14.9.tgz",
|
||||
"integrity": "sha512-3gtUX0e584MYkKBQMgSECMvE1Dwzg+eONefDQ0wxVSe5YMBsZwdN5pL7UapwWBlV8+i8QCztF9TP947tEjZAGA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"idb": "7.1.1",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/app-check": {
|
||||
"version": "0.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-check/-/app-check-0.11.1.tgz",
|
||||
"integrity": "sha512-gmKfwQ2k8aUQlOyRshc+fOQLq0OwUmibIZvpuY1RDNu2ho0aTMlwxOuEiJeYOs7AxzhSx7gnXPFNsXCFbnvXUQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/app-check-compat": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-check-compat/-/app-check-compat-0.4.1.tgz",
|
||||
"integrity": "sha512-yjSvSl5B1u4CirnxhzirN1uiTRCRfx+/qtfbyeyI+8Cx8Cw1RWAIO/OqytPSVwLYbJJ1vEC3EHfxazRaMoWKaA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app-check": "0.11.1",
|
||||
"@firebase/app-check-types": "0.5.3",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/app-check-interop-types": {
|
||||
"version": "0.3.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-check-interop-types/-/app-check-interop-types-0.3.3.tgz",
|
||||
"integrity": "sha512-gAlxfPLT2j8bTI/qfe3ahl2I2YcBQ8cFIBdhAQA4I2f3TndcO+22YizyGYuttLHPQEpWkhmpFW60VCFEPg4g5A==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/app-check-types": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-check-types/-/app-check-types-0.5.3.tgz",
|
||||
"integrity": "sha512-hyl5rKSj0QmwPdsAxrI5x1otDlByQ7bvNvVt8G/XPO2CSwE++rmSVf3VEhaeOR4J8ZFaF0Z0NDSmLejPweZ3ng==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/app-compat": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-compat/-/app-compat-0.5.9.tgz",
|
||||
"integrity": "sha512-e5LzqjO69/N2z7XcJeuMzIp4wWnW696dQeaHAUpQvGk89gIWHAIvG6W+mA3UotGW6jBoqdppEJ9DnuwbcBByug==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app": "0.14.9",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/app-types": {
|
||||
"version": "0.9.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/app-types/-/app-types-0.9.3.tgz",
|
||||
"integrity": "sha512-kRVpIl4vVGJ4baogMDINbyrIOtOxqhkZQg4jTq3l8Lw6WSk0xfpEYzezFu+Kl4ve4fbPl79dvwRtaFqAC/ucCw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/auth": {
|
||||
"version": "1.12.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth/-/auth-1.12.1.tgz",
|
||||
"integrity": "sha512-nXKj7d5bMBlnq6XpcQQpmnSVwEeHBkoVbY/+Wk0P1ebLSICoH4XPtvKOFlXKfIHmcS84mLQ99fk3njlDGKSDtw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x",
|
||||
"@react-native-async-storage/async-storage": "^2.2.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@react-native-async-storage/async-storage": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/auth-compat": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth-compat/-/auth-compat-0.6.3.tgz",
|
||||
"integrity": "sha512-nHOkupcYuGVxI1AJJ/OBhLPaRokbP14Gq4nkkoVvf1yvuREEWqdnrYB/CdsSnPxHMAnn5wJIKngxBF9jNX7s/Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/auth": "1.12.1",
|
||||
"@firebase/auth-types": "0.13.0",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/auth-interop-types": {
|
||||
"version": "0.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth-interop-types/-/auth-interop-types-0.2.4.tgz",
|
||||
"integrity": "sha512-JPgcXKCuO+CWqGDnigBtvo09HeBs5u/Ktc2GaFj2m01hLarbxthLNm7Fk8iOP1aqAtXV+fnnGj7U28xmk7IwVA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/auth-types": {
|
||||
"version": "0.13.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/auth-types/-/auth-types-0.13.0.tgz",
|
||||
"integrity": "sha512-S/PuIjni0AQRLF+l9ck0YpsMOdE8GO2KU6ubmBB7P+7TJUCQDa3R1dlgYm9UzGbbePMZsp0xzB93f2b/CgxMOg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@firebase/app-types": "0.x",
|
||||
"@firebase/util": "1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/component": {
|
||||
"version": "0.7.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/component/-/component-0.7.1.tgz",
|
||||
"integrity": "sha512-mFzsm7CLHR60o08S23iLUY8m/i6kLpOK87wdEFPLhdlCahaxKmWOwSVGiWoENYSmFJJoDhrR3gKSCxz7ENdIww==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/data-connect": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/data-connect/-/data-connect-0.4.0.tgz",
|
||||
"integrity": "sha512-vLXM6WHNIR3VtEeYNUb/5GTsUOyl3Of4iWNZHBe1i9f88sYFnxybJNWVBjvJ7flhCyF8UdxGpzWcUnv6F5vGfg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/auth-interop-types": "0.2.4",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/database": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/database/-/database-1.1.1.tgz",
|
||||
"integrity": "sha512-LwIXe8+mVHY5LBPulWECOOIEXDiatyECp/BOlu0gOhe+WOcKjWHROaCbLlkFTgHMY7RHr5MOxkLP/tltWAH3dA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app-check-interop-types": "0.3.3",
|
||||
"@firebase/auth-interop-types": "0.2.4",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"faye-websocket": "0.11.4",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/database-compat": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/database-compat/-/database-compat-2.1.1.tgz",
|
||||
"integrity": "sha512-heAEVZ9Z8c8PnBUcmGh91JHX0cXcVa1yESW/xkLuwaX7idRFyLiN8sl73KXpR8ZArGoPXVQDanBnk6SQiekRCQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/database": "1.1.1",
|
||||
"@firebase/database-types": "1.0.17",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/database-types": {
|
||||
"version": "1.0.17",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/database-types/-/database-types-1.0.17.tgz",
|
||||
"integrity": "sha512-4eWaM5fW3qEIHjGzfi3cf0Jpqi1xQsAdT6rSDE1RZPrWu8oGjgrq6ybMjobtyHQFgwGCykBm4YM89qDzc+uG/w==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app-types": "0.9.3",
|
||||
"@firebase/util": "1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/firestore": {
|
||||
"version": "4.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore/-/firestore-4.12.0.tgz",
|
||||
"integrity": "sha512-PM47OyiiAAoAMB8kkq4Je14mTciaRoAPDd3ng3Ckqz9i2TX9D9LfxIRcNzP/OxzNV4uBKRq6lXoOggkJBQR3Gw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"@firebase/webchannel-wrapper": "1.0.5",
|
||||
"@grpc/grpc-js": "~1.9.0",
|
||||
"@grpc/proto-loader": "^0.7.8",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/firestore-compat": {
|
||||
"version": "0.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore-compat/-/firestore-compat-0.4.6.tgz",
|
||||
"integrity": "sha512-NgVyR4hHHN2FvSNQOtbgBOuVsEdD/in30d9FKbEvvITiAChrBN2nBstmhfjI4EOTnHaP8zigwvkNYFI9yKGAkQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/firestore": "4.12.0",
|
||||
"@firebase/firestore-types": "3.0.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/firestore-types": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/firestore-types/-/firestore-types-3.0.3.tgz",
|
||||
"integrity": "sha512-hD2jGdiWRxB/eZWF89xcK9gF8wvENDJkzpVFb4aGkzfEaKxVRD1kjz1t1Wj8VZEp2LCB53Yx1zD8mrhQu87R6Q==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@firebase/app-types": "0.x",
|
||||
"@firebase/util": "1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/functions": {
|
||||
"version": "0.13.2",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/functions/-/functions-0.13.2.tgz",
|
||||
"integrity": "sha512-tHduUD+DeokM3NB1QbHCvEMoL16e8Z8JSkmuVA4ROoJKPxHn8ibnecHPO2e3nVCJR1D9OjuKvxz4gksfq92/ZQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/app-check-interop-types": "0.3.3",
|
||||
"@firebase/auth-interop-types": "0.2.4",
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/messaging-interop-types": "0.2.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/functions-compat": {
|
||||
"version": "0.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/functions-compat/-/functions-compat-0.4.2.tgz",
|
||||
"integrity": "sha512-YNxgnezvZDkqxqXa6cT7/oTeD4WXbxgIP7qZp4LFnathQv5o2omM6EoIhXiT9Ie5AoQDcIhG9Y3/dj+DFJGaGQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/functions": "0.13.2",
|
||||
"@firebase/functions-types": "0.6.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/functions-types": {
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/functions-types/-/functions-types-0.6.3.tgz",
|
||||
"integrity": "sha512-EZoDKQLUHFKNx6VLipQwrSMh01A1SaL3Wg6Hpi//x6/fJ6Ee4hrAeswK99I5Ht8roiniKHw4iO0B1Oxj5I4plg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/installations": {
|
||||
"version": "0.6.20",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/installations/-/installations-0.6.20.tgz",
|
||||
"integrity": "sha512-LOzvR7XHPbhS0YB5ANXhqXB5qZlntPpwU/4KFwhSNpXNsGk/sBQ9g5hepi0y0/MfenJLe2v7t644iGOOElQaHQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/util": "1.14.0",
|
||||
"idb": "7.1.1",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/installations-compat": {
|
||||
"version": "0.2.20",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/installations-compat/-/installations-compat-0.2.20.tgz",
|
||||
"integrity": "sha512-9C9pL/DIEGucmoPj8PlZTnztbX3nhNj5RTYVpUM7wQq/UlHywaYv99969JU/WHLvi9ptzIogXYS9d1eZ6XFe9g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/installations-types": "0.5.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/installations-types": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/installations-types/-/installations-types-0.5.3.tgz",
|
||||
"integrity": "sha512-2FJI7gkLqIE0iYsNQ1P751lO3hER+Umykel+TkLwHj6plzWVxqvfclPUZhcKFVQObqloEBTmpi2Ozn7EkCABAA==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@firebase/app-types": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/logger": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/logger/-/logger-0.5.0.tgz",
|
||||
"integrity": "sha512-cGskaAvkrnh42b3BA3doDWeBmuHFO/Mx5A83rbRDYakPjO9bJtRL3dX7javzc2Rr/JHZf4HlterTW2lUkfeN4g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/messaging": {
|
||||
"version": "0.12.24",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/messaging/-/messaging-0.12.24.tgz",
|
||||
"integrity": "sha512-UtKoubegAhHyehcB7iQjvQ8OVITThPbbWk3g2/2ze42PrQr6oe6OmCElYQkBrE5RDCeMTNucXejbdulrQ2XwVg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/messaging-interop-types": "0.2.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"idb": "7.1.1",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/messaging-compat": {
|
||||
"version": "0.2.24",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/messaging-compat/-/messaging-compat-0.2.24.tgz",
|
||||
"integrity": "sha512-wXH8FrKbJvFuFe6v98TBhAtvgknxKIZtGM/wCVsfpOGmaAE80bD8tBxztl+uochjnFb9plihkd6mC4y7sZXSpA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/messaging": "0.12.24",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/messaging-interop-types": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/messaging-interop-types/-/messaging-interop-types-0.2.3.tgz",
|
||||
"integrity": "sha512-xfzFaJpzcmtDjycpDeCUj0Ge10ATFi/VHVIvEEjDNc3hodVBQADZ7BWQU7CuFpjSHE+eLuBI13z5F/9xOoGX8Q==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/performance": {
|
||||
"version": "0.7.10",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/performance/-/performance-0.7.10.tgz",
|
||||
"integrity": "sha512-8nRFld+Ntzp5cLKzZuG9g+kBaSn8Ks9dmn87UQGNFDygbmR6ebd8WawauEXiJjMj1n70ypkvAOdE+lzeyfXtGA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0",
|
||||
"web-vitals": "^4.2.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/performance-compat": {
|
||||
"version": "0.2.23",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/performance-compat/-/performance-compat-0.2.23.tgz",
|
||||
"integrity": "sha512-c7qOAGBUAOpIuUlHu1axWcrCVtIYKPMhH0lMnoCDWnPwn1HcPuPUBVTWETbC7UWw71RMJF8DpirfWXzMWJQfgA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/performance": "0.7.10",
|
||||
"@firebase/performance-types": "0.2.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/performance-types": {
|
||||
"version": "0.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/performance-types/-/performance-types-0.2.3.tgz",
|
||||
"integrity": "sha512-IgkyTz6QZVPAq8GSkLYJvwSLr3LS9+V6vNPQr0x4YozZJiLF5jYixj0amDtATf1X0EtYHqoPO48a9ija8GocxQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/remote-config": {
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/remote-config/-/remote-config-0.8.1.tgz",
|
||||
"integrity": "sha512-L86TReBnPiiJOWd7k9iaiE9f7rHtMpjAoYN0fH2ey2ZRzsOChHV0s5sYf1+IIUYzplzsE46pjlmAUNkRRKwHSQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/remote-config-compat": {
|
||||
"version": "0.2.22",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/remote-config-compat/-/remote-config-compat-0.2.22.tgz",
|
||||
"integrity": "sha512-uW/eNKKtRBot2gnCC5mnoy5Voo2wMzZuQ7dwqqGHU176fO9zFgMwKiRzk+aaC99NLrFk1KOmr0ZVheD+zdJmjQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/logger": "0.5.0",
|
||||
"@firebase/remote-config": "0.8.1",
|
||||
"@firebase/remote-config-types": "0.5.0",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/remote-config-types": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/remote-config-types/-/remote-config-types-0.5.0.tgz",
|
||||
"integrity": "sha512-vI3bqLoF14L/GchtgayMiFpZJF+Ao3uR8WCde0XpYNkSokDpAKca2DxvcfeZv7lZUqkUwQPL2wD83d3vQ4vvrg==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@firebase/storage": {
|
||||
"version": "0.14.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/storage/-/storage-0.14.1.tgz",
|
||||
"integrity": "sha512-uIpYgBBsv1vIET+5xV20XT7wwqV+H4GFp6PBzfmLUcEgguS4SWNFof56Z3uOC2lNDh0KDda1UflYq2VwD9Nefw==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/storage-compat": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/storage-compat/-/storage-compat-0.4.1.tgz",
|
||||
"integrity": "sha512-bgl3FHHfXAmBgzIK/Fps6Xyv2HiAQlSTov07CBL+RGGhrC5YIk4lruS8JVIC+UkujRdYvnf8cpQFGn2RCilJ/A==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/component": "0.7.1",
|
||||
"@firebase/storage": "0.14.1",
|
||||
"@firebase/storage-types": "0.8.3",
|
||||
"@firebase/util": "1.14.0",
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@firebase/app-compat": "0.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/storage-types": {
|
||||
"version": "0.8.3",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/storage-types/-/storage-types-0.8.3.tgz",
|
||||
"integrity": "sha512-+Muk7g9uwngTpd8xn9OdF/D48uiQ7I1Fae7ULsWPuKoCH3HU7bfFPhxtJYzyhjdniowhuDpQcfPmuNRAqZEfvg==",
|
||||
"license": "Apache-2.0",
|
||||
"peerDependencies": {
|
||||
"@firebase/app-types": "0.x",
|
||||
"@firebase/util": "1.x"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/util": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/util/-/util-1.14.0.tgz",
|
||||
"integrity": "sha512-/gnejm7MKkVIXnSJGpc9L2CvvvzJvtDPeAEq5jAwgVlf/PeNxot+THx/bpD20wQ8uL5sz0xqgXy1nisOYMU+mw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"tslib": "^2.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@firebase/webchannel-wrapper": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@firebase/webchannel-wrapper/-/webchannel-wrapper-1.0.5.tgz",
|
||||
"integrity": "sha512-+uGNN7rkfn41HLO0vekTFhTxk61eKa8mTpRGLO0QSqlQdKvIoGAvLp3ppdVIWbTGYJWM6Kp0iN+PjMIOcnVqTw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@grpc/grpc-js": {
|
||||
"version": "1.9.16",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.9.16.tgz",
|
||||
"integrity": "sha512-wE4Ut/olIzfKqp631XrG+wbF0v1vWFN4YL9FyXC2LJiG33DsV7PLzURjrCvY/6je2ntdRkeLpPDluzSRGaVltQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@grpc/proto-loader": "^0.7.8",
|
||||
"@types/node": ">=12.12.47"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^8.13.0 || >=10.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@grpc/proto-loader": {
|
||||
"version": "0.7.15",
|
||||
"resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.7.15.tgz",
|
||||
"integrity": "sha512-tMXdRCfYVixjuFK+Hk0Q1s38gV9zDiDJfWL3h1rv4Qc39oILCu1TRTDt7+fGUI8K4G1Fj125Hx/ru3azECWTyQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"lodash.camelcase": "^4.3.0",
|
||||
"long": "^5.0.0",
|
||||
"protobufjs": "^7.2.5",
|
||||
"yargs": "^17.7.2"
|
||||
},
|
||||
"bin": {
|
||||
"proto-loader-gen-types": "build/bin/proto-loader-gen-types.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/fs-minipass": {
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
|
||||
@@ -2786,6 +3429,63 @@
|
||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/aspromise": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
|
||||
"integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/base64": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz",
|
||||
"integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/codegen": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz",
|
||||
"integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/eventemitter": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz",
|
||||
"integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/fetch": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz",
|
||||
"integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@protobufjs/float": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz",
|
||||
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/path": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
|
||||
"integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/pool": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz",
|
||||
"integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@protobufjs/utf8": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz",
|
||||
"integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@radix-ui/primitive": {
|
||||
"version": "1.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.7.tgz",
|
||||
@@ -2983,6 +3683,55 @@
|
||||
"react-native": "^0.0.0-0 || >=0.65 <1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-firebase/app": {
|
||||
"version": "23.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-23.8.8.tgz",
|
||||
"integrity": "sha512-9KeKToqsEPhaL+KlTYIdUB0bsXYAcKk0Zzpu608zf2qsXbyTscuYs1caLWxk8AdGtt3+j7ZsoOFt/8qnlo4Ugg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"firebase": "12.10.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": ">=47.0.0",
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-firebase/auth": {
|
||||
"version": "23.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-firebase/auth/-/auth-23.8.8.tgz",
|
||||
"integrity": "sha512-2X067hMAW07O1vKvQh5LtnvlCNfgoKOFXeStTurhnA3+BslkASJwKzjs0qUvnE8RJ7R2YltiF/E+nJfplOKDuQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"plist": "^3.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-firebase/app": "23.8.8",
|
||||
"expo": ">=47.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"expo": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native-firebase/firestore": {
|
||||
"version": "23.8.8",
|
||||
"resolved": "https://registry.npmjs.org/@react-native-firebase/firestore/-/firestore-23.8.8.tgz",
|
||||
"integrity": "sha512-oGD7EN/NHEz/U5GJNJVZtUF17G4hCp36uB4kjdJ6+UCFn+vgfGS/uMio2kXCMVBO6bmgI2qbfClWryirZ3xmcQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"react-native-url-polyfill": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@react-native-firebase/app": "23.8.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@react-native/assets-registry": {
|
||||
"version": "0.81.5",
|
||||
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.81.5.tgz",
|
||||
@@ -5002,6 +5751,19 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/expo-build-properties": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-1.0.10.tgz",
|
||||
"integrity": "sha512-mFCZbrbrv0AP5RB151tAoRzwRJelqM7bCJzCkxpu+owOyH+p/rFC/q7H5q8B9EpVWj8etaIuszR+gKwohpmu1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ajv": "^8.11.0",
|
||||
"semver": "^7.6.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"expo": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/expo-constants": {
|
||||
"version": "18.0.14",
|
||||
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-18.0.14.tgz",
|
||||
@@ -6027,6 +6789,18 @@
|
||||
],
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/faye-websocket": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz",
|
||||
"integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"websocket-driver": ">=0.5.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/fb-watchman": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
|
||||
@@ -6133,6 +6907,42 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/firebase": {
|
||||
"version": "12.10.0",
|
||||
"resolved": "https://registry.npmjs.org/firebase/-/firebase-12.10.0.tgz",
|
||||
"integrity": "sha512-tAjHnEirksqWpa+NKDUSUMjulOnsTcsPC1X1rQ+gwPtjlhJS572na91CwaBXQJHXharIrfj7sw/okDkXOsphjA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@firebase/ai": "2.9.0",
|
||||
"@firebase/analytics": "0.10.20",
|
||||
"@firebase/analytics-compat": "0.2.26",
|
||||
"@firebase/app": "0.14.9",
|
||||
"@firebase/app-check": "0.11.1",
|
||||
"@firebase/app-check-compat": "0.4.1",
|
||||
"@firebase/app-compat": "0.5.9",
|
||||
"@firebase/app-types": "0.9.3",
|
||||
"@firebase/auth": "1.12.1",
|
||||
"@firebase/auth-compat": "0.6.3",
|
||||
"@firebase/data-connect": "0.4.0",
|
||||
"@firebase/database": "1.1.1",
|
||||
"@firebase/database-compat": "2.1.1",
|
||||
"@firebase/firestore": "4.12.0",
|
||||
"@firebase/firestore-compat": "0.4.6",
|
||||
"@firebase/functions": "0.13.2",
|
||||
"@firebase/functions-compat": "0.4.2",
|
||||
"@firebase/installations": "0.6.20",
|
||||
"@firebase/installations-compat": "0.2.20",
|
||||
"@firebase/messaging": "0.12.24",
|
||||
"@firebase/messaging-compat": "0.2.24",
|
||||
"@firebase/performance": "0.7.10",
|
||||
"@firebase/performance-compat": "0.2.23",
|
||||
"@firebase/remote-config": "0.8.1",
|
||||
"@firebase/remote-config-compat": "0.2.22",
|
||||
"@firebase/storage": "0.14.1",
|
||||
"@firebase/storage-compat": "0.4.1",
|
||||
"@firebase/util": "1.14.0"
|
||||
}
|
||||
},
|
||||
"node_modules/flow-enums-runtime": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz",
|
||||
@@ -6343,6 +7153,12 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/http-parser-js": {
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.10.tgz",
|
||||
"integrity": "sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/https-proxy-agent": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
@@ -6362,6 +7178,12 @@
|
||||
"integrity": "sha512-WDC/ui2VVRrz3jOVi+XtjqkDjiVjTtFaAGiW37k6b+ohyQ5wYDOGkvCZa8+H0nx3gyvv0+BST9xuOgIyGQ00gw==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/idb": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/idb/-/idb-7.1.1.tgz",
|
||||
"integrity": "sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -7357,6 +8179,12 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/lodash.camelcase": {
|
||||
"version": "4.3.0",
|
||||
"resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
|
||||
"integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/lodash.debounce": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
|
||||
@@ -7381,6 +8209,12 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/long": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
|
||||
"integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/loose-envify": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
|
||||
@@ -8466,6 +9300,29 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "7.6.6",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.6.tgz",
|
||||
"integrity": "sha512-dYDWdjSl5RNb7SgPxGQcRU+GtvP7s2fpkrY0r432PcOIaZ0/rBcxEZnQN67iJhFuQiVw754JDoPruPCNdGsbjg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
"@protobufjs/base64": "^1.1.2",
|
||||
"@protobufjs/codegen": "^2.0.5",
|
||||
"@protobufjs/eventemitter": "^1.1.1",
|
||||
"@protobufjs/fetch": "^1.1.1",
|
||||
"@protobufjs/float": "^1.0.2",
|
||||
"@protobufjs/path": "^1.1.2",
|
||||
"@protobufjs/pool": "^1.1.0",
|
||||
"@protobufjs/utf8": "^1.1.1",
|
||||
"@types/node": ">=13.7.0",
|
||||
"long": "^5.3.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/punycode": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||
@@ -8696,6 +9553,18 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-url-polyfill": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/react-native-url-polyfill/-/react-native-url-polyfill-3.0.0.tgz",
|
||||
"integrity": "sha512-aA5CiuUCUb/lbrliVCJ6lZ17/RpNJzvTO/C7gC/YmDQhTUoRD5q5HlJfwLWcxz4VgAhHwXKzhxH+wUN24tAdqg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url-without-unicode": "8.0.0-3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/react-native-web": {
|
||||
"version": "0.21.2",
|
||||
"resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz",
|
||||
@@ -10376,6 +11245,12 @@
|
||||
"defaults": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"node_modules/web-vitals": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/web-vitals/-/web-vitals-4.2.4.tgz",
|
||||
"integrity": "sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz",
|
||||
@@ -10385,6 +11260,29 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-driver": {
|
||||
"version": "0.7.5",
|
||||
"resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.5.tgz",
|
||||
"integrity": "sha512-ZL2+3c7kMBdIRCMz6l8jQMHyGVxj+UL+xVk74Ombiciboca8rHa15L86B19E5oh1pL9Ii/uj54gtsIrZGMo6zA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"http-parser-js": ">=0.5.1",
|
||||
"safe-buffer": ">=5.1.0",
|
||||
"websocket-extensions": ">=0.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-extensions": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz",
|
||||
"integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==",
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/whatwg-fetch": {
|
||||
"version": "3.6.20",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
|
||||
|
||||
@@ -12,7 +12,11 @@
|
||||
"dependencies": {
|
||||
"@expo/metro-runtime": "~6.1.2",
|
||||
"@react-native-async-storage/async-storage": "2.2.0",
|
||||
"@react-native-firebase/app": "23.8.8",
|
||||
"@react-native-firebase/auth": "23.8.8",
|
||||
"@react-native-firebase/firestore": "23.8.8",
|
||||
"expo": "~54.0.36",
|
||||
"expo-build-properties": "~1.0.10",
|
||||
"expo-constants": "~18.0.14",
|
||||
"expo-dev-client": "~6.0.21",
|
||||
"expo-font": "~14.0.12",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 149 KiB |
@@ -0,0 +1,9 @@
|
||||
const {test}=require('node:test');const assert=require('node:assert/strict');const ts=require('typescript');const fs=require('node:fs');const vm=require('node:vm');
|
||||
const source=ts.transpileModule(fs.readFileSync('src/firebase/state-mapping.ts','utf8'),{compilerOptions:{module:ts.ModuleKind.CommonJS,target:ts.ScriptTarget.ES2022}}).outputText;
|
||||
const sandbox={exports:{},Date};vm.runInNewContext(source,sandbox);const {initialState,toUserPatch,mergePatch,fromAccount}=sandbox.exports;
|
||||
const plain=v=>JSON.parse(JSON.stringify(v));
|
||||
test('only business edits cross cloud boundary',()=>{const state={...initialState,values:{'565:123':'72',duration:'5 MIN'},toggles:{vibration:true}};assert.deepEqual(plain(toUserPatch(initialState,state)),{});});
|
||||
test('explicit profile edit maps to stable fields',()=>{assert.deepEqual(plain(toUserPatch(initialState,{...initialState,values:{firstName:'Léon',birthYear:'1998',gender:'Homme'}})),{profile:{firstName:'Léon',birthYear:1998,gender:'male'}});});
|
||||
test('partial invalid year is not uploaded',()=>{assert.deepEqual(plain(toUserPatch(initialState,{...initialState,values:{birthYear:'19'}})),{});});
|
||||
test('queue merges fields without losing unsent edits',()=>{assert.deepEqual(plain(mergePatch({profile:{firstName:'A'},onboarding:{experience:'watch'}},{profile:{birthYear:2000}})),{profile:{firstName:'A',birthYear:2000},onboarding:{experience:'watch'}});});
|
||||
test('cloud hydration does not use mock profile',()=>{const s=fromAccount({profile:{firstName:'',birthYear:null,gender:null},onboarding:{completed:false,experience:'phone'},preferences:{blackScreenEnabled:false}},{...initialState,values:{firstName:'Alexandre'}});assert.equal(s.values.firstName,'');});
|
||||
@@ -33,6 +33,7 @@ export function DesignScreen({id, preview=false}: {id:string; preview?:boolean})
|
||||
const scale = Math.min(width,600)/402;
|
||||
const header = scene?.layers.find(l=>/Header - TopAppBar|Header - Top App Bar/.test(l.name));
|
||||
const footer = scene?.layers.find(l=>/Bottom Navigation/.test(l.name));
|
||||
const accountLayers = scene?.layers.filter(l=>l.text === 'COMPTE ET ABONNEMENT'||l.text==='Se déconnecter')??[];
|
||||
const actionLayers = useMemo(()=>scene?.layers.filter(l=>l.action && !scene.layers.some(parent=>parent.action&&l.path.includes(parent.id)))??[],[scene]);
|
||||
const timerLayer = scene?.layers.find(l=>l.text && /^\d{1,2}:\d{2}$/.test(l.text.trim()));
|
||||
const navigate = (target:string, replace=false) => {const href={pathname:'/screen/[id]' as const,params:{id:target.replace(':','-')}}; replace?router.replace(href):router.push(href);};
|
||||
@@ -66,6 +67,7 @@ export function DesignScreen({id, preview=false}: {id:string; preview?:boolean})
|
||||
}
|
||||
function replacement(layer:Layer) {
|
||||
if(preview)return undefined;
|
||||
if(layer.text==='Alexandre'&&['565:3818','580:119'].includes(id))return state.values.firstName||'—';
|
||||
if(layer.text?.includes('[Prénom]')&&state.values.firstName)return layer.text.replace('[Prénom]',state.values.firstName);
|
||||
if(isSession&&layer.id===timerLayer?.id)return time(remaining);
|
||||
const slider=scene?.layers.find(l=>l.action?.type==='slider'&&l.action.valueNode===layer.id)?.action;
|
||||
@@ -95,7 +97,7 @@ export function DesignScreen({id, preview=false}: {id:string; preview?:boolean})
|
||||
if(!preview&&toggleTracks.includes(layer)){const on=!!state.toggles[layer.action?.key??''];return <View key={layer.id} pointerEvents="none" style={{position:'absolute',left:layer.x*scale,top:(layer.y-offsetY)*scale,width:layer.w*scale,height:layer.h*scale,borderRadius:999,backgroundColor:on?theme.accent:'#3e484e'}}><View style={{position:'absolute',top:4*scale,left:(on?layer.w-20:4)*scale,width:16*scale,height:16*scale,borderRadius:8*scale,backgroundColor:on?'#003548':theme.muted}} /></View>;}
|
||||
const element=<DesignLayer key={layer.id} layer={layer} scale={scale} offsetY={offsetY} replacement={replacement(layer)} />;
|
||||
return isSession&&!preview&&layer.t==='ELLIPSE' ? <Animated.View key={layer.id} pointerEvents="none" style={{position:'absolute',inset:0,opacity:pulse.interpolate({inputRange:[1,1.035],outputRange:[.75,1]})}}>{element}</Animated.View> : element;
|
||||
})}{!preview&&controls(layers,offsetY,scaleOverride)}</>;
|
||||
})}{!preview&&controls(layers,offsetY,scaleOverride)}{!preview&&accountLayers.filter(l=>layers.includes(l)).map(accountLayer=><Pressable key={'account-'+accountLayer.id} accessibilityRole="button" accessibilityLabel="Mon compte" onPress={()=>router.push('/account')} hitSlop={12} style={{position:'absolute',left:accountLayer.x*scale,top:(accountLayer.y-offsetY)*scale,width:accountLayer.w*scale,height:Math.max(32,accountLayer.h*scale)}}/>)}</>;
|
||||
}
|
||||
const top=preview?0:insets.top;
|
||||
const bottom=preview?0:insets.bottom;
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import React,{createContext,use,useEffect,useState} from 'react';
|
||||
import {onAuthStateChanged,type FirebaseAuthTypes} from '@react-native-firebase/auth';
|
||||
import {auth} from './client';
|
||||
const Context=createContext<{user:FirebaseAuthTypes.User|null;ready:boolean}>({user:null,ready:false});
|
||||
export function AuthProvider({children}:{children:React.ReactNode}) {
|
||||
const [value,setValue]=useState<{user:FirebaseAuthTypes.User|null;ready:boolean}>({user:null,ready:false});
|
||||
useEffect(()=>onAuthStateChanged(auth,user=>setValue({user,ready:true})),[]);
|
||||
return <Context value={value}>{children}</Context>;
|
||||
}
|
||||
export const useAuth=()=>use(Context);
|
||||
@@ -0,0 +1,11 @@
|
||||
import {getApp} from '@react-native-firebase/app';
|
||||
import {getAuth} from '@react-native-firebase/auth';
|
||||
import {getFirestore} from '@react-native-firebase/firestore';
|
||||
|
||||
// Default app is configured by the native GoogleService-Info.plist.
|
||||
export const firebaseApp = getApp();
|
||||
export const auth = getAuth(firebaseApp);
|
||||
export const database = getFirestore(firebaseApp);
|
||||
if (firebaseApp.options.projectId !== 'haptika-d13d3') {
|
||||
throw new Error('Configuration Firebase Haptika incorrecte.');
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import {doc, getDoc, getDocs, collection, query, orderBy, limit, runTransaction, serverTimestamp, Timestamp, type FirebaseFirestoreTypes} from '@react-native-firebase/firestore';
|
||||
import {auth, database} from './client';
|
||||
|
||||
export type Profile = {firstName:string;birthYear:number|null;gender:'female'|'male'|null;locale:string;timeZone:string};
|
||||
export type Onboarding = {stress:'low'|'medium'|'high'|null;sleep:'low'|'medium'|'high'|null;energy:'low'|'medium'|'high'|null;experience:'phone'|'watch';completed:boolean;flowVersion:1};
|
||||
export type Preferences = {notificationsEnabled:boolean;saveMindAndBodyWorkouts:boolean;blackScreenEnabled:boolean};
|
||||
export type UserPatch = {profile?:Partial<Profile>;onboarding?:Partial<Onboarding>;preferences?:Partial<Preferences>};
|
||||
export const defaults = {
|
||||
profile:{firstName:'',birthYear:null,gender:null,locale:'fr',timeZone:Intl.DateTimeFormat().resolvedOptions().timeZone || 'Europe/Paris'} as Profile,
|
||||
onboarding:{stress:null,sleep:null,energy:null,experience:'phone',completed:false,flowVersion:1} as Onboarding,
|
||||
preferences:{notificationsEnabled:false,saveMindAndBodyWorkouts:false,blackScreenEnabled:false} as Preferences,
|
||||
};
|
||||
function checkOwner(uid:string) {if(auth.currentUser?.uid!==uid)throw new Error('Le compte a changé.');}
|
||||
export async function ensureProfile(uid:string) {
|
||||
checkOwner(uid);
|
||||
const ref=doc(database,'users',uid);
|
||||
await runTransaction(database,async tx=>{const snapshot=await tx.get(ref);checkOwner(uid);if(!snapshot.exists())tx.set(ref,{...defaults.profile,schemaVersion:1,accountState:'active',createdAt:serverTimestamp(),updatedAt:serverTimestamp()});else if(snapshot.data()?.accountState!=='active')throw new Error('Ce compte est en cours de suppression.');});
|
||||
}
|
||||
export async function readAccount(uid:string) {
|
||||
checkOwner(uid);
|
||||
const refs=[doc(database,'users',uid),doc(database,'users',uid,'onboarding','current'),doc(database,'users',uid,'preferences','general')];
|
||||
const [p,o,s]=await Promise.all(refs.map(ref=>getDoc(ref)));
|
||||
checkOwner(uid);
|
||||
if(p.data()?.accountState!=='active')throw new Error('Ce compte est indisponible.');
|
||||
return {profile:{...defaults.profile,...p.data()} as Profile,onboarding:{...defaults.onboarding,...o.data()} as Onboarding,preferences:{...defaults.preferences,...s.data()} as Preferences};
|
||||
}
|
||||
export async function applyUserPatch(uid:string, patch:UserPatch) {
|
||||
checkOwner(uid);
|
||||
const entries=([
|
||||
['profile',doc(database,'users',uid)],['onboarding',doc(database,'users',uid,'onboarding','current')],['preferences',doc(database,'users',uid,'preferences','general')],
|
||||
] as const).filter(([kind])=>patch[kind]);
|
||||
await runTransaction(database,async tx=>{
|
||||
const snapshots=await Promise.all(entries.map(([,ref])=>tx.get(ref)));
|
||||
checkOwner(uid);
|
||||
entries.forEach(([kind,ref],i)=>{const snap=snapshots[i];const previous=snap.data();if(kind==='profile' && previous?.accountState!=='active')throw new Error('Compte indisponible.');tx.set(ref,{...defaults[kind],...previous,...patch[kind],schemaVersion:1,createdAt:previous?.createdAt??serverTimestamp(),updatedAt:serverTimestamp()});});
|
||||
});
|
||||
}
|
||||
export type Measurement = {source:'iphoneCameraPPG'|'appleWatchOptical';deviceId:string;startedAt:FirebaseFirestoreTypes.Timestamp;endedAt:FirebaseFirestoreTypes.Timestamp;durationSeconds:number;averageBpm:number|null;minimumBpm:number|null;maximumBpm:number|null;estimatedRmssdMs:number|null;sdnnMs:number|null;quality:number;coverage:number;valid:boolean;algorithmVersion:string};
|
||||
export async function saveMeasurement(uid:string,id:string,value:Measurement) {
|
||||
checkOwner(uid);
|
||||
if(!/^[a-zA-Z0-9-]{1,100}$/.test(id))throw new Error('Identifiant de mesure invalide.');
|
||||
const ref=doc(database,'users',uid,'measurements',id);
|
||||
return runTransaction(database,async tx=>{const [existing,deleted]=await Promise.all([tx.get(ref),tx.get(doc(database,'users',uid,'tombstones','measurement_'+id))]);checkOwner(uid);if(deleted.exists())throw new Error('Mesure supprimée.');if(existing.exists())return 'already-saved';tx.set(ref,{...value,schemaVersion:1,createdAt:serverTimestamp(),updatedAt:serverTimestamp()});return 'saved';});
|
||||
}
|
||||
export async function recentMeasurements(uid:string) {checkOwner(uid);const snapshot=await getDocs(query(collection(database,'users',uid,'measurements'),orderBy('startedAt','desc'),limit(30)));checkOwner(uid);return snapshot.docs.map((d:FirebaseFirestoreTypes.QueryDocumentSnapshot)=>({id:d.id,...d.data()}));}
|
||||
export {Timestamp};
|
||||
|
||||
export type Session = {
|
||||
practiceId:'breathing'|'anchoring'|'meditation'|'sleep'|'wake'|'coherence';protocolVersion:number;deviceId:string;source:Measurement['source'];
|
||||
startedAt:FirebaseFirestoreTypes.Timestamp;endedAt:FirebaseFirestoreTypes.Timestamp|null;plannedDurationSeconds:number;actualDurationSeconds:number;
|
||||
status:'inProgress'|'awaitingAfter'|'completed'|'cancelled';beforeMeasurementId:string|null;afterMeasurementId:string|null;feelingBefore:number|null;feelingAfter:number|null;
|
||||
};
|
||||
export async function saveSession(uid:string,id:string,value:Session,expectedRevision:number) {
|
||||
checkOwner(uid);
|
||||
if(!/^[a-zA-Z0-9-]{1,100}$/.test(id))throw new Error('Identifiant de séance invalide.');
|
||||
const ref=doc(database,'users',uid,'sessions',id);
|
||||
return runTransaction(database,async tx=>{
|
||||
const [snapshot,deleted]=await Promise.all([tx.get(ref),tx.get(doc(database,'users',uid,'tombstones','session_'+id))]);checkOwner(uid);
|
||||
if(deleted.exists())throw new Error('Séance supprimée.');
|
||||
const previous=snapshot.data();
|
||||
if((previous?.revision??0)!==expectedRevision)throw new Error('Cette séance a été modifiée. Recharge-la avant de réessayer.');
|
||||
tx.set(ref,{...value,schemaVersion:1,revision:expectedRevision+1,createdAt:previous?.createdAt??serverTimestamp(),updatedAt:serverTimestamp()});
|
||||
return expectedRevision+1;
|
||||
});
|
||||
}
|
||||
export async function recentSessions(uid:string) {checkOwner(uid);const snapshot=await getDocs(query(collection(database,'users',uid,'sessions'),orderBy('startedAt','desc'),limit(30)));checkOwner(uid);return snapshot.docs.map((d:FirebaseFirestoreTypes.QueryDocumentSnapshot)=>({id:d.id,...d.data()}));}
|
||||
@@ -0,0 +1,23 @@
|
||||
import type {UserPatch,Profile,Onboarding,Preferences} from './repository';
|
||||
export type LocalState={onboarded:boolean;variant:'phone'|'watch';values:Record<string,string>;toggles:Record<string,boolean>};
|
||||
export const initialState:LocalState={onboarded:false,variant:'phone',values:{},toggles:{}};
|
||||
// Only deliberate edits to supported business fields are synced. Source frame IDs,
|
||||
// example values, timer state and unimplemented sensors never cross this boundary.
|
||||
export function toUserPatch(before:LocalState,after:LocalState):UserPatch {
|
||||
const p:UserPatch={};
|
||||
if(before.values.firstName!==after.values.firstName)p.profile={firstName:after.values.firstName??''};
|
||||
if(before.values.birthYear!==after.values.birthYear){const value=after.values.birthYear??'';if(value===''||(/^\d{4}$/.test(value)&&Number(value)>=1900&&Number(value)<=new Date().getFullYear()))p.profile={...p.profile,birthYear:value?Number(value):null};}
|
||||
if(before.values.gender!==after.values.gender)p.profile={...p.profile,gender:after.values.gender==='Homme'?'male':after.values.gender==='Femme'?'female':null};
|
||||
if(before.variant!==after.variant)p.onboarding={experience:after.variant};
|
||||
if(before.onboarded!==after.onboarded)p.onboarding={...p.onboarding,completed:after.onboarded};
|
||||
if(before.toggles['mode ecran noir']!==after.toggles['mode ecran noir'])p.preferences={blackScreenEnabled:!!after.toggles['mode ecran noir']};
|
||||
return p;
|
||||
}
|
||||
export function mergePatch(a:UserPatch,b:UserPatch):UserPatch {
|
||||
const result={...a};
|
||||
for(const key of ['profile','onboarding','preferences'] as const)if(b[key])result[key]={...a[key],...b[key]} as never;
|
||||
return result;
|
||||
}
|
||||
export function fromAccount(account:{profile:Profile;onboarding:Onboarding;preferences:Preferences},local:LocalState):LocalState {
|
||||
return {...local,onboarded:account.onboarding.completed,variant:account.onboarding.experience,values:{...local.values,firstName:account.profile.firstName,birthYear:account.profile.birthYear?.toString()??'',gender:account.profile.gender==='male'?'Homme':account.profile.gender==='female'?'Femme':''},toggles:{...local.toggles,'mode ecran noir':account.preferences.blackScreenEnabled}};
|
||||
}
|
||||
@@ -1,15 +1,61 @@
|
||||
import React, {createContext, use, useEffect, useState} from 'react';
|
||||
import React,{createContext,use,useEffect,useRef,useState} from 'react';
|
||||
import AsyncStorage from '@react-native-async-storage/async-storage';
|
||||
type State = {onboarded: boolean; variant: 'phone' | 'watch'; values: Record<string, string>; toggles: Record<string, boolean>};
|
||||
const initial: State = {onboarded: false, variant: 'phone', values: {}, toggles: {}};
|
||||
const key = 'haptika.mobile.preferences.v1';
|
||||
const Context = createContext<{state: State; ready: boolean; update: (patch: Partial<State>) => void}>({state: initial, ready: false, update: () => {}});
|
||||
export function AppProvider({children}: {children: React.ReactNode}) {
|
||||
const [state, setState] = useState(initial);
|
||||
const [ready, setReady] = useState(false);
|
||||
useEffect(() => {let active = true; AsyncStorage.getItem(key).then(value => {if (active && value) {const saved = JSON.parse(value);setState({...initial, ...saved});}}).catch(() => {}).finally(() => {if (active) setReady(true);});return () => {active = false;};}, []);
|
||||
useEffect(() => {if (ready) AsyncStorage.setItem(key, JSON.stringify(state)).catch(() => {});}, [state, ready]);
|
||||
const update = (patch: Partial<State>) => setState(previous => ({...previous, ...patch}));
|
||||
return <Context value={{state, ready, update}}>{children}</Context>;
|
||||
import {useAuth} from '../firebase/auth-state';
|
||||
import {ensureProfile,readAccount,applyUserPatch,type UserPatch} from '../firebase/repository';
|
||||
import {initialState,toUserPatch,mergePatch,fromAccount,type LocalState} from '../firebase/state-mapping';
|
||||
import {auth} from '../firebase/client';
|
||||
|
||||
type SyncStatus='local'|'loading'|'pending'|'synced'|'error';
|
||||
const Context=createContext<{state:LocalState;ready:boolean;syncStatus:SyncStatus;syncError:string|null;retry:()=>void;update:(patch:Partial<LocalState>)=>void}>({state:initialState,ready:false,syncStatus:'local',syncError:null,retry:()=>{},update:()=>{}});
|
||||
export function AppProvider({children}:{children:React.ReactNode}) {
|
||||
const {user,ready}=useAuth();
|
||||
if(!ready)return null;
|
||||
return <AccountState key={user?.uid??'local'} uid={user?.uid}>{children}</AccountState>;
|
||||
}
|
||||
export const useAppState = () => use(Context);
|
||||
function AccountState({children,uid}:{children:React.ReactNode;uid?:string}) {
|
||||
const key=uid?`haptika.mobile.account.v1.${uid}`:'haptika.mobile.preferences.v1';
|
||||
const [state,setState]=useState<LocalState>(initialState);
|
||||
const [ready,setReady]=useState(false);
|
||||
const [syncStatus,setStatus]=useState<SyncStatus>(uid?'loading':'local');
|
||||
const [syncError,setError]=useState<string|null>(null);
|
||||
const current=useRef(initialState),pending=useRef<UserPatch>({}),busy=useRef(false),active=useRef(true),storageChain=useRef(Promise.resolve());
|
||||
const hydrated=useRef(false),remoteLoaded=useRef(false),version=useRef(0);
|
||||
const persist=()=>{const value=JSON.stringify(uid?{state:current.current,pending:pending.current}:current.current);storageChain.current=storageChain.current.catch(()=>{}).then(()=>AsyncStorage.setItem(key,value));return storageChain.current;};
|
||||
async function flush() {
|
||||
if(!uid||busy.current||!active.current||!hydrated.current||auth.currentUser?.uid!==uid)return;
|
||||
busy.current=true;
|
||||
try {
|
||||
await storageChain.current;
|
||||
await ensureProfile(uid);
|
||||
const sent=pending.current, sentVersion=version.current;
|
||||
if(Object.keys(sent).length)await applyUserPatch(uid,sent);
|
||||
if(!active.current)return;
|
||||
if(version.current===sentVersion){pending.current={};await persist();if(!remoteLoaded.current){const account=await readAccount(uid);if(!active.current)return;if(version.current===sentVersion){current.current=fromAccount(account,current.current);setState(current.current);remoteLoaded.current=true;await persist();}}setStatus(Object.keys(pending.current).length?'pending':'synced');}else setStatus('pending');
|
||||
setError(null);
|
||||
}catch(error){if(active.current){setStatus('error');setError(error instanceof Error?error.message:'Synchronisation indisponible.');}}
|
||||
finally{busy.current=false;}
|
||||
}
|
||||
useEffect(()=>{
|
||||
active.current=true;
|
||||
void (async()=>{
|
||||
try {
|
||||
const raw=await AsyncStorage.getItem(key);if(!active.current)return;
|
||||
if(raw){const saved=JSON.parse(raw);current.current={...initialState,...(uid?saved.state:saved)};pending.current=uid?(saved.pending??{}):{};setState(current.current);}
|
||||
hydrated.current=true;setReady(true);
|
||||
if(uid)await flush();
|
||||
}catch(error){if(active.current){setStatus('error');setError(error instanceof Error?error.message:'Stockage indisponible.');}}
|
||||
finally{if(active.current){hydrated.current=true;setReady(true);}}
|
||||
})();
|
||||
const timer=setInterval(()=>{if(Object.keys(pending.current).length)void flush();},10000);
|
||||
return()=>{active.current=false;clearInterval(timer);};
|
||||
},[key]);
|
||||
const update=(patch:Partial<LocalState>)=>{
|
||||
if(!ready)return;
|
||||
const next={...current.current,...patch};
|
||||
if(uid){pending.current=mergePatch(pending.current,toUserPatch(current.current,next));version.current++;if(Object.keys(pending.current).length)setStatus('pending');}
|
||||
current.current=next;setState(next);
|
||||
void persist().then(()=>flush()).catch(()=>{setStatus('error');setError('Impossible de sauvegarder les modifications sur cet appareil.');});
|
||||
};
|
||||
return <Context value={{state,ready,syncStatus,syncError,retry:()=>{void persist().then(()=>flush()).catch(()=>setError('Stockage indisponible.'));},update}}>{children}</Context>;
|
||||
}
|
||||
export const useAppState=()=>use(Context);
|
||||
|
||||
Reference in New Issue
Block a user