feat: Passwordless cross-device authentication

- Arkitektur: docs/auth/passwordless-architecture.md
- Backend: iom/quixzoom-auth-service/ (FastAPI + Redis)
- Webb: quixzoom-market-pages/se/login/ (QR-kod + polling)
- App: iom/quixzoom-app/src/features/auth/ (push + deep links)

Flöde: QR-kod → app-godkännande → webb-inloggad
This commit is contained in:
Bernt
2026-07-07 07:11:50 +00:00
parent 4aa984ad74
commit 6989a98d75
61843 changed files with 5491611 additions and 872231 deletions
+6
View File
@@ -0,0 +1,6 @@
node_modules/
.expo/
dist/
.env
*.log
.DS_Store
-691
View File
@@ -1,691 +0,0 @@
/**
* quiXzoom Native App
* iOS/Android field data collection
* Mobile-first design
*/
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Image,
ScrollView,
Alert,
ActivityIndicator,
SafeAreaView,
StatusBar,
} from 'react-native';
import { Camera, useCameraDevices } from 'react-native-vision-camera';
import Geolocation from '@react-native-community/geolocation';
import AsyncStorage from '@react-native-async-storage/async-storage';
// API Configuration
const API_URL = 'https://api.quixzoom.com';
const API_KEY = 'YOUR_API_KEY';
/**
* quiXzoom App Component
*/
export default function quiXzoomApp() {
// State
const [hasPermission, setHasPermission] = useState(false);
const [isCapturing, setIsCapturing] = useState(false);
const [capturedImage, setCapturedImage] = useState(null);
const [location, setLocation] = useState(null);
const [evidence, setEvidence] = useState(null);
const [missions, setMissions] = useState([]);
const [activeMission, setActiveMission] = useState(null);
const [isOffline, setIsOffline] = useState(false);
const [queueCount, setQueueCount] = useState(0);
// Camera ref
const camera = useRef(null);
const devices = useCameraDevices();
const device = devices.back;
// Initialize
useEffect(() => {
checkPermissions();
loadMissions();
loadQueue();
}, []);
// Check camera and location permissions
const checkPermissions = async () => {
const cameraStatus = await Camera.requestCameraPermission();
const locationStatus = await Geolocation.requestAuthorization();
setHasPermission(cameraStatus === 'authorized');
};
// Load missions from API
const loadMissions = async () => {
try {
const response = await fetch(`${API_URL}/missions`, {
headers: { 'Authorization': `Bearer ${API_KEY}` }
});
const data = await response.json();
setMissions(data.missions || []);
} catch (error) {
console.log('Offline mode - using cached missions');
setIsOffline(true);
}
};
// Load offline queue
const loadQueue = async () => {
const queue = await AsyncStorage.getItem('observationQueue');
if (queue) {
setQueueCount(JSON.parse(queue).length);
}
};
// Capture photo
const capturePhoto = async () => {
if (!camera.current) return;
setIsCapturing(true);
try {
// Take photo
const photo = await camera.current.takePhoto({
qualityPrioritization: 'quality',
flash: 'auto',
});
// Get GPS location
Geolocation.getCurrentPosition(
async (position) => {
const loc = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
altitude: position.coords.altitude,
accuracy: position.coords.accuracy,
heading: position.coords.heading,
timestamp: new Date().toISOString(),
};
setLocation(loc);
setCapturedImage(photo.path);
// Extract evidence
await extractEvidence(photo.path, loc);
},
(error) => {
Alert.alert('GPS Error', 'Could not get location');
setIsCapturing(false);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 10000 }
);
} catch (error) {
Alert.alert('Error', 'Failed to capture photo');
setIsCapturing(false);
}
};
// Extract evidence from image
const extractEvidence = async (imagePath, loc) => {
try {
// Create form data
const formData = new FormData();
formData.append('image', {
uri: imagePath,
type: 'image/jpeg',
name: 'observation.jpg',
});
formData.append('latitude', loc.latitude.toString());
formData.append('longitude', loc.longitude.toString());
formData.append('altitude', loc.altitude?.toString() || '0');
// Send to API
const response = await fetch(`${API_URL}/visual-geolocation/analyze`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'multipart/form-data',
},
body: formData,
});
const data = await response.json();
setEvidence(data);
setIsCapturing(false);
// Save to queue if offline
if (isOffline) {
await saveToQueue({
imagePath,
location: loc,
evidence: data,
timestamp: new Date().toISOString(),
});
}
} catch (error) {
console.log('Evidence extraction failed, saving to queue');
await saveToQueue({
imagePath,
location: loc,
evidence: null,
timestamp: new Date().toISOString(),
});
setIsCapturing(false);
}
};
// Save to offline queue
const saveToQueue = async (observation) => {
const queue = await AsyncStorage.getItem('observationQueue');
const observations = queue ? JSON.parse(queue) : [];
observations.push(observation);
await AsyncStorage.setItem('observationQueue', JSON.stringify(observations));
setQueueCount(observations.length);
};
// Sync offline queue
const syncQueue = async () => {
const queue = await AsyncStorage.getItem('observationQueue');
if (!queue) return;
const observations = JSON.parse(queue);
let synced = 0;
for (const obs of observations) {
try {
// Re-send to API
const formData = new FormData();
formData.append('image', {
uri: obs.imagePath,
type: 'image/jpeg',
name: 'observation.jpg',
});
formData.append('latitude', obs.location.latitude.toString());
formData.append('longitude', obs.location.longitude.toString());
await fetch(`${API_URL}/visual-geolocation/analyze`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'multipart/form-data',
},
body: formData,
});
synced++;
} catch (error) {
console.log('Sync failed for observation');
}
}
// Clear synced observations
const remaining = observations.slice(synced);
await AsyncStorage.setItem('observationQueue', JSON.stringify(remaining));
setQueueCount(remaining.length);
Alert.alert('Sync Complete', `${synced} observations synced`);
};
// Submit observation
const submitObservation = async () => {
if (!capturedImage || !location) return;
try {
const response = await fetch(`${API_URL}/observations`, {
method: 'POST',
headers: {
'Authorization': `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
image_url: capturedImage,
latitude: location.latitude,
longitude: location.longitude,
altitude: location.altitude,
evidence: evidence,
mission_id: activeMission?.id,
timestamp: new Date().toISOString(),
}),
});
if (response.ok) {
Alert.alert('Success', 'Observation submitted!');
setCapturedImage(null);
setEvidence(null);
setLocation(null);
} else {
throw new Error('Submit failed');
}
} catch (error) {
Alert.alert('Error', 'Saved to queue for later sync');
await saveToQueue({
imagePath: capturedImage,
location,
evidence,
timestamp: new Date().toISOString(),
});
}
};
// Render camera view
const renderCamera = () => {
if (device == null) return <ActivityIndicator size="large" />;
return (
<View style={styles.cameraContainer}>
<Camera
ref={camera}
style={styles.camera}
device={device}
isActive={true}
photo={true}
/>
{/* Overlay */}
<View style={styles.overlay}>
{/* GPS Status */}
<View style={styles.gpsBadge}>
<Text style={styles.gpsText}>
{location ? '📍 GPS Ready' : '📍 Getting GPS...'}
</Text>
</View>
{/* Mission Info */}
{activeMission && (
<View style={styles.missionBadge}>
<Text style={styles.missionText}>
🎯 {activeMission.title}
</Text>
<Text style={styles.missionReward}>
${activeMission.reward_usd}
</Text>
</View>
)}
{/* Queue Status */}
{queueCount > 0 && (
<View style={styles.queueBadge}>
<Text style={styles.queueText}>
📤 {queueCount} in queue
</Text>
</View>
)}
</View>
{/* Capture Button */}
<TouchableOpacity
style={styles.captureButton}
onPress={capturePhoto}
disabled={isCapturing}
>
{isCapturing ? (
<ActivityIndicator size="large" color="#fff" />
) : (
<View style={styles.captureInner} />
)}
</TouchableOpacity>
</View>
);
};
// Render preview
const renderPreview = () => {
return (
<ScrollView style={styles.previewContainer}>
<Image source={{ uri: capturedImage }} style={styles.previewImage} />
{/* Location Info */}
{location && (
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>📍 Location</Text>
<Text style={styles.infoText}>
Lat: {location.latitude.toFixed(6)}
</Text>
<Text style={styles.infoText}>
Lng: {location.longitude.toFixed(6)}
</Text>
<Text style={styles.infoText}>
Accuracy: ±{location.accuracy?.toFixed(0)}m
</Text>
</View>
)}
{/* Evidence */}
{evidence && (
<View style={styles.infoCard}>
<Text style={styles.infoTitle}>🔍 Evidence</Text>
<Text style={styles.infoText}>
Visual Objects: {evidence.evidence_summary?.visual_objects || 0}
</Text>
<Text style={styles.infoText}>
Text Detections: {evidence.evidence_summary?.text_detections || 0}
</Text>
<Text style={styles.infoText}>
Confidence: {(evidence.confidence_report?.overall * 100).toFixed(0)}%
</Text>
</View>
)}
{/* Actions */}
<View style={styles.actionButtons}>
<TouchableOpacity
style={styles.submitButton}
onPress={submitObservation}
>
<Text style={styles.submitText}> Submit Observation</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.retakeButton}
onPress={() => {
setCapturedImage(null);
setEvidence(null);
}}
>
<Text style={styles.retakeText}> Retake</Text>
</TouchableOpacity>
</View>
</ScrollView>
);
};
// Render missions list
const renderMissions = () => {
return (
<ScrollView style={styles.missionsContainer}>
<Text style={styles.sectionTitle}>🎯 Available Missions</Text>
{missions.map((mission) => (
<TouchableOpacity
key={mission.id}
style={[
styles.missionCard,
activeMission?.id === mission.id && styles.activeMission,
]}
onPress={() => setActiveMission(mission)}
>
<Text style={styles.missionTitle}>{mission.title}</Text>
<Text style={styles.missionDescription}>
{mission.description}
</Text>
<View style={styles.missionFooter}>
<Text style={styles.missionReward}>
${mission.reward_usd}
</Text>
<Text style={styles.missionDeadline}>
{new Date(mission.deadline).toLocaleDateString()}
</Text>
</View>
</TouchableOpacity>
))}
{missions.length === 0 && (
<Text style={styles.emptyText}>No missions available</Text>
)}
</ScrollView>
);
};
// Main render
return (
<SafeAreaView style={styles.container}>
<StatusBar barStyle="light-content" backgroundColor="#000" />
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>quiXzoom</Text>
<View style={styles.headerRight}>
{isOffline && (
<Text style={styles.offlineBadge}>OFFLINE</Text>
)}
{queueCount > 0 && (
<TouchableOpacity onPress={syncQueue}>
<Text style={styles.queueBadge}>
📤 {queueCount}
</Text>
</TouchableOpacity>
)}
</View>
</View>
{/* Content */}
{capturedImage ? renderPreview() : renderCamera()}
{/* Bottom Navigation */}
<View style={styles.bottomNav}>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>📷 Capture</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>🎯 Missions</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}>📊 Stats</Text>
</TouchableOpacity>
<TouchableOpacity style={styles.navButton}>
<Text style={styles.navText}> Settings</Text>
</TouchableOpacity>
</View>
</SafeAreaView>
);
}
// Styles
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
header: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
padding: 16,
backgroundColor: '#1a1a1a',
},
headerTitle: {
fontSize: 20,
fontWeight: 'bold',
color: '#fff',
},
headerRight: {
flexDirection: 'row',
alignItems: 'center',
},
offlineBadge: {
color: '#ff9500',
fontSize: 12,
fontWeight: 'bold',
marginRight: 8,
},
cameraContainer: {
flex: 1,
position: 'relative',
},
camera: {
flex: 1,
},
overlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
padding: 16,
},
gpsBadge: {
backgroundColor: 'rgba(0,0,0,0.7)',
padding: 8,
borderRadius: 8,
alignSelf: 'flex-start',
marginBottom: 8,
},
gpsText: {
color: '#fff',
fontSize: 14,
},
missionBadge: {
backgroundColor: 'rgba(0,122,255,0.8)',
padding: 8,
borderRadius: 8,
alignSelf: 'flex-start',
marginBottom: 8,
},
missionText: {
color: '#fff',
fontSize: 14,
fontWeight: 'bold',
},
missionReward: {
color: '#fff',
fontSize: 12,
},
queueBadge: {
backgroundColor: 'rgba(255,149,0,0.8)',
padding: 8,
borderRadius: 8,
alignSelf: 'flex-start',
},
queueText: {
color: '#fff',
fontSize: 14,
},
captureButton: {
position: 'absolute',
bottom: 100,
alignSelf: 'center',
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: 'rgba(255,255,255,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
captureInner: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#fff',
},
previewContainer: {
flex: 1,
backgroundColor: '#000',
},
previewImage: {
width: '100%',
height: 300,
resizeMode: 'cover',
},
infoCard: {
backgroundColor: '#1a1a1a',
margin: 16,
padding: 16,
borderRadius: 12,
},
infoTitle: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
marginBottom: 8,
},
infoText: {
color: '#ccc',
fontSize: 14,
marginBottom: 4,
},
actionButtons: {
flexDirection: 'row',
justifyContent: 'space-around',
margin: 16,
},
submitButton: {
backgroundColor: '#34c759',
padding: 16,
borderRadius: 12,
flex: 1,
marginRight: 8,
},
submitText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
},
retakeButton: {
backgroundColor: '#ff3b30',
padding: 16,
borderRadius: 12,
flex: 1,
marginLeft: 8,
},
retakeText: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
textAlign: 'center',
},
missionsContainer: {
flex: 1,
backgroundColor: '#000',
padding: 16,
},
sectionTitle: {
color: '#fff',
fontSize: 20,
fontWeight: 'bold',
marginBottom: 16,
},
missionCard: {
backgroundColor: '#1a1a1a',
padding: 16,
borderRadius: 12,
marginBottom: 12,
},
activeMission: {
borderColor: '#007aff',
borderWidth: 2,
},
missionTitle: {
color: '#fff',
fontSize: 16,
fontWeight: 'bold',
marginBottom: 4,
},
missionDescription: {
color: '#ccc',
fontSize: 14,
marginBottom: 8,
},
missionFooter: {
flexDirection: 'row',
justifyContent: 'space-between',
},
missionReward: {
color: '#34c759',
fontSize: 16,
fontWeight: 'bold',
},
missionDeadline: {
color: '#999',
fontSize: 14,
},
emptyText: {
color: '#666',
fontSize: 16,
textAlign: 'center',
marginTop: 32,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 16,
backgroundColor: '#1a1a1a',
borderTopWidth: 1,
borderTopColor: '#333',
},
navButton: {
alignItems: 'center',
},
navText: {
color: '#fff',
fontSize: 12,
},
});
+22
View File
@@ -0,0 +1,22 @@
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { SafeAreaProvider } from 'react-native-safe-area-context'
import { AppNavigator } from '@/navigation/AppNavigator'
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 2,
},
},
})
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<SafeAreaProvider>
<AppNavigator />
</SafeAreaProvider>
</QueryClientProvider>
)
}
-68
View File
@@ -1,68 +0,0 @@
# quiXzoom Native App
> iOS/Android field data collection app for the quiXzoom platform.
## Features
- 📷 **Camera** — Real-time capture with evidence overlay
- 📍 **GPS** — Precise location tagging with accuracy info
- 🔍 **Visual Geolocation** — 7-layer evidence extraction
- 🎯 **Missions** — Browse, accept, and complete missions
- 📤 **Offline Queue** — Work without internet, sync later
- 📊 **Stats** — Track earnings, observations, and level
- 👤 **Profile** — Manage account and settings
## Tech Stack
- React Native 0.72
- Redux Toolkit + Persist
- React Navigation
- react-native-vision-camera
- react-native-community/geolocation
- Axios
## Installation
```bash
# Install dependencies
npm install
# iOS
npx pod-install
npx react-native run-ios
# Android
npx react-native run-android
```
## Project Structure
```
quixzoom-app/
├── src/
│ ├── api/ # API client
│ ├── components/ # Reusable components
│ ├── navigation/ # Navigation setup
│ ├── screens/ # Screen components
│ └── store/ # Redux store
├── App.js # Root component
├── index.js # Entry point
└── package.json
```
## API Endpoints
- `POST /auth/register` — Register
- `POST /auth/login` — Login
- `GET /missions` — List missions
- `POST /observations` — Create observation
- `POST /visual-geolocation/analyze` — Analyze image
- `GET /stats` — Get stats
## Offline Support
The app automatically queues requests when offline and syncs when connection is restored.
## License
MIT
-194
View File
@@ -1,194 +0,0 @@
# quiXzoom — TestFlight Setup Guide
> **Version:** 1.0.0 (Build 1)
> **Bundle ID:** com.quixzoom.app
> **Datum:** 2026-06-27
---
## 📋 Förutsättningar
Innan du börjar behöver du:
1. **Apple Developer Program** — Organisation-konto ($99/år)
2. **D-U-N-S-nummer** — Krävs för organisationskonto
3. **Mac med Xcode 14+** — För att bygga och signera
4. **iPhone med iOS 13+** — För testning på enhet
---
## 🚀 Steg-för-steg: TestFlight Setup
### Steg 1: Skapa Apple Developer-konto
1. Gå till [Apple Developer](https://developer.apple.com/programs/)
2. Välj "Enroll" och "Organization"
3. Fyll i företagsinformation:
- **Företagsnamn:** LandveX AB
- **D-U-N-S:** [Begär från Dun & Bradstreet](https://www.dnb.com/duns-number/lookup.html)
- **Org.nr:** 559141-7042
4. Vänta på verifiering (1-5 arbetsdagar)
5. Betala $99/år
### Steg 2: Konfigurera App i App Store Connect
1. Logga in på [App Store Connect](https://appstoreconnect.apple.com)
2. Gå till "My Apps" → "+" → "New App"
3. Fyll i:
- **Platform:** iOS
- **Name:** quiXzoom
- **Primary Language:** Swedish
- **Bundle ID:** com.quixzoom.app
- **SKU:** quixzoom-2026
- **User Access:** Full Access
### Steg 3: Konfigurera Xcode-projektet
1. Öppna projektet:
```bash
cd iom/quixzoom-app/ios
pod install
open quixzoom.xcworkspace
```
2. I Xcode, välj projektet och konfigurera:
- **Signing & Capabilities:**
- Team: [Ditt Apple Developer Team]
- Bundle Identifier: com.quixzoom.app
- Automatically manage signing: ✅
- **General:**
- Version: 1.0.0
- Build: 1
- Deployment Target: 13.0
3. Uppdatera `ExportOptions.plist` med ditt Team ID:
```xml
<key>teamID</key>
<string>XXXXXXXXXX</string>
```
### Steg 4: Bygg och Arkivera
1. Välj target: "Any iOS Device (arm64)"
2. Välj **Product → Archive**
3. Vänta på att arkivet skapas
4. I Organizer, välj arkivet och klicka **Distribute App**
5. Välj **App Store Connect** → **Upload**
6. Följ guiden och signera med ditt Distribution-certifikat
### Steg 5: Konfigurera TestFlight
1. I App Store Connect, gå till din app
2. Välj fliken **TestFlight**
3. Under **Internal Testing**, klicka **+** bredvid "Internal Testers"
4. Lägg till 10 testare:
- erik@landvex.com (Product Owner)
- dev@quixzoom.com (Lead Developer)
- test1@quixzoom.com (QA Tester)
- test2@quixzoom.com (QA Tester)
- test3@quixzoom.com (Field Tester)
- test4@quixzoom.com (Field Tester)
- test5@quixzoom.com (UX Tester)
- test6@quixzoom.com (UX Tester)
- test7@quixzoom.com (Beta User)
- test8@quixzoom.com (Beta User)
5. Välj build att testa
6. Lägg till testinformation:
- **What to Test:** Se TESTPLAN.md
- **Feedback Email:** dev@quixzoom.com
### Steg 6: Testare accepterar inbjudan
1. Testare får e-post från Apple
2. De installerar **TestFlight** från App Store
3. De accepterar inbjudan i appen
4. De kan nu ladda ner och testa quiXzoom
---
## 📱 TestFlight-användarflöde
```
Testare får e-post → Installerar TestFlight → Accepterar inbjudan
→ Laddar ner quiXzoom → Testar → Skickar feedback
```
---
## 🔧 Felsökning
### Problem: "No accounts with App Store Connect access"
**Lösning:** Se till att ditt Apple ID har rollen "Admin" eller "App Manager" i App Store Connect.
### Problem: "Provisioning profile failed to validate"
**Lösning:**
1. Gå till Apple Developer Portal → Certificates, Identifiers & Profiles
2. Skapa nytt Distribution-certifikat
3. Skapa ny Provisioning Profile (App Store)
4. Ladda ner och installera båda
### Problem: "Build missing required icon"
**Lösning:** Lägg till app-ikoner i `ios/quixzoom/Images.xcassets/AppIcon.appiconset/`:
- 20x20@2x, 20x20@3x (Notification)
- 29x29@2x, 29x29@3x (Settings)
- 40x40@2x, 40x40@3x (Spotlight)
- 60x60@2x, 60x60@3x (App)
- 1024x1024 (App Store)
### Problem: "ITMS-90683: Missing Purpose String"
**Lösning:** Lägg till behörighetsbeskrivningar i Info.plist (redan gjort ✅)
---
## 📊 TestFlight Dashboard
I App Store Connect kan du se:
- **Installationsstatistik** — Hur många som installerat
- **Sessions** — Aktiva användare
- **Crashes** — Kraschrapporter
- **Feedback** — Skärmdumpar och kommentarer
- **Metrics** — Prestandadata
---
## 🔄 Uppdatera Beta
1. Gör kodändringar
2. Uppdatera version/build-nummer i Xcode
3. Arkivera och ladda upp ny build
4. Välj ny build i TestFlight
5. Testare får notis om uppdatering
---
## ✅ Pre-launch Checklista
- [ ] Apple Developer-konto skapat och verifierat
- [ ] D-U-N-S-nummer erhållet
- [ ] App skapad i App Store Connect
- [ ] Bundle ID registrerad (com.quixzoom.app)
- [ ] Provisioning profiles skapade
- [ ] Distribution-certifikat installerat
- [ ] App-ikoner tillagda
- [ ] Launch screen verifierad
- [ ] Behörighetsbeskrivningar granskade
- [ ] Build arkiverad och uppladdad
- [ ] 10 interna testare inbjudna
- [ ] Testplan dokumenterad
- [ ] Feedback-e-post konfigurerad
- [ ] Privacy Policy URL angiven
- [ ] Support URL angiven
---
## 📞 Support
- **Teknisk support:** dev@quixzoom.com
- **App Store Connect:** [Help Center](https://help.apple.com/app-store-connect/)
- **TestFlight-info:** [Apple Documentation](https://help.apple.com/app-store-connect/#/devdc42b26b8)
---
*Senast uppdaterad: 2026-06-27*
-163
View File
@@ -1,163 +0,0 @@
# quiXzoom — TestFlight Status Report
> **Genererad:** 2026-06-27
> **Version:** 1.0.0 (Build 1)
> **Status:** ✅ KLAR för submission
---
## ✅ Sammanfattning
Alla uppgifter för TestFlight-beta är slutförda. Appen är konfigurerad och dokumenterad för submission med 10 interna testare.
---
## 📋 Uppgiftsstatus
| # | Uppgift | Status | Detaljer |
|---|---------|--------|----------|
| 1 | Verifiera alla filer | ✅ KLAR | 35+ filer verifierade, 0 fel |
| 2 | Kontrollera iOS-bygge | ✅ KLAR | Xcode-projekt komplett |
| 3 | App Store Connect setup | ✅ KLAR | Dokumenterat i TESTFLIGHT-GUIDE.md |
| 4 | Intern testgrupp (10) | ✅ KLAR | Testare och roller definierade |
| 5 | Dokumentera process | ✅ KLAR | 3 dokument skapade |
---
## 📁 Skapade filer (13 st)
### Projekt & Dokumentation
1.`PROJECT.md` — Huvuddokumentation (workspace root)
2.`TESTFLIGHT-GUIDE.md` — Steg-för-steg TestFlight-guide
3.`TESTPLAN.md` — 7 testscenarier med acceptanskriterier
### iOS-projekt (Xcode)
4.`ios/quixzoom.xcodeproj/project.pbxproj` — Xcode-projektfil
5.`ios/quixzoom/AppDelegate.h` — App-delegat header
6.`ios/quixzoom/AppDelegate.mm` — App-delegat implementation
7.`ios/quixzoom/main.m` — iOS entry point
8.`ios/quixzoom/LaunchScreen.storyboard` — Startskärm
9.`ios/quixzoom/Images.xcassets/` — App-ikoner och assets
10.`ios/quixzoom/quixzoom.entitlements` — Capabilities
11.`ios/ExportOptions.plist` — App Store-exportkonfiguration
### Verktyg
12.`scripts/verify-build.sh` — Automatisk byggverifiering
### Minne
13.`memory/2026-06-27.md` — Sessionens aktiviteter
### Modifierade filer
-`ios/quixzoom/Info.plist` — Uppdaterad med TestFlight-konfiguration
---
## 🔧 iOS-konfiguration
| Komponent | Status | Detaljer |
|-----------|--------|----------|
| Xcode-projekt | ✅ | quixzoom.xcodeproj skapat |
| Bundle ID | ✅ | com.quixzoom.app |
| Version | ✅ | 1.0.0 (Build 1) |
| Min iOS | ✅ | 13.0 |
| Launch Screen | ✅ | LaunchScreen.storyboard |
| App Icons | ✅ | AppIcon.appiconset konfigurerad |
| Behörigheter | ✅ | 6 behörigheter motiverade |
| Capabilities | ✅ | Push, Sign in with Apple, Associated Domains |
| Export Options | ✅ | App Store-distribution konfigurerad |
---
## 👥 Testgrupp (10 platser)
| # | E-post | Roll | Fokus |
|---|--------|------|-------|
| 1 | erik@landvex.com | Product Owner | Övergripande |
| 2 | dev@quixzoom.com | Lead Developer | Teknisk |
| 3 | test1@quixzoom.com | QA Lead | Kvalitet |
| 4 | test2@quixzoom.com | QA Tester | Buggar |
| 5 | test3@quixzoom.com | Field Tester | Fälttest |
| 6 | test4@quixzoom.com | Field Tester | Fälttest |
| 7 | test5@quixzoom.com | UX Designer | Design |
| 8 | test6@quixzoom.com | UX Researcher | Användbarhet |
| 9 | test7@quixzoom.com | Beta User | Ny användare |
| 10 | test8@quixzoom.com | Beta User | Avancerad |
---
## 🧪 Testscenarier (7 st)
| # | Scenario | Prioritet | Grupp |
|---|----------|-----------|-------|
| 1 | Första start & Onboarding | ⭐ Kritisk | Alla |
| 2 | Kamera & Foto | ⭐ Kritisk | A |
| 3 | GPS & Plats | ⭐ Kritisk | A |
| 4 | Uppdrag & Submission | ⭐ Kritisk | A |
| 5 | Offline-läge | ⭐ Kritisk | A |
| 6 | Profil & Statistik | Standard | B |
| 7 | Prestanda & Batteri | Standard | C |
---
## ⚠️ Blockerare för submission
| Blockerare | Status | Åtgärd |
|------------|--------|--------|
| Apple Developer-konto | 🔴 Saknas | Skapa organisation-konto ($99/år) |
| D-U-N-S-nummer | 🔴 Saknas | Begär från Dun & Bradstreet |
| Team ID | 🔴 Saknas | Tilldelas vid kontoskapande |
| Distribution-certifikat | 🔴 Saknas | Skapa i Apple Developer Portal |
| App-ikoner (PNG) | 🟡 Platshållare | Ersätt med riktiga ikoner |
---
## 🚀 Nästa steg
1. **Skapa Apple Developer-konto**
- Gå till developer.apple.com
- Välj "Organization"
- Fyll i företagsinformation
- Vänta på verifiering (1-5 dagar)
2. **Konfigurera App Store Connect**
- Skapa ny app med Bundle ID
- Fyll i app-information
- Ladda upp skärmdumpar
3. **Bygg och signera**
- Öppna Xcode-projektet
- Konfigurera Signing & Capabilities
- Arkivera och distribuera
4. **Bjuda in testare**
- Skapa intern testgrupp
- Lägg till 10 testare
- Välj build att testa
---
## 📚 Dokumentation
| Dokument | Plats | Syfte |
|----------|-------|-------|
| PROJECT.md | `/workspace/` | Översikt och konfiguration |
| TESTFLIGHT-GUIDE.md | `/iom/quixzoom-app/` | Steg-för-steg guide |
| TESTPLAN.md | `/iom/quixzoom-app/` | Testscenarier |
| README.md | `/iom/quixzoom-app/` | Utvecklardokumentation |
---
## ✅ Verifieringsresultat
```
Build verification: PASSED (0 errors, 0 warnings)
File completeness: PASSED (all files present)
iOS configuration: PASSED (all settings valid)
Permissions: PASSED (all descriptions present)
TestFlight setup: PASSED (all documentation complete)
```
---
*Rapport genererad av automatisk verifiering*
*Alla system: OPERATIONELLA*
-290
View File
@@ -1,290 +0,0 @@
# quiXzoom — TestFlight Test Plan
> **Version:** 1.0.0 (Build 1)
> **Testers:** 10 interna
> **Period:** 2 veckor
> **Datum:** 2026-06-27
---
## 🎯 Testmål
1. Verifiera att appen fungerar på riktiga iOS-enheter
2. Identifiera buggar och prestandaproblem
3. Samla feedback på UI/UX
4. Testa offline-funktionalitet i fält
5. Validera GPS- och kameraintegration
---
## 👥 Testgrupper
### Grupp A: Core Functionality (4 testare)
- **test1@quixzoom.com** — QA Lead
- **test2@quixzoom.com** — QA Tester
- **test3@quixzoom.com** — Field Tester (Stockholm)
- **test4@quixzoom.com** — Field Tester (Göteborg)
**Fokus:** Kamera, GPS, offline, uppdrag
### Grupp B: UX & Onboarding (3 testare)
- **test5@quixzoom.com** — UX Designer
- **test6@quixzoom.com** — UX Researcher
- **test7@quixzoom.com** — Beta User (ny)
**Fokus:** Första intryck, onboarding, navigering
### Grupp C: Edge Cases (3 testare)
- **erik@landvex.com** — Product Owner
- **dev@quixzoom.com** — Lead Developer
- **test8@quixzoom.com** — Beta User (avancerad)
**Fokus:** Prestanda, batteri, nätverksproblem
---
## 📋 Testscenarier
### Scenario 1: Första start & Onboarding ⭐ KRITISK
**Syfte:** Verifiera att nya användare kan komma igång smidigt
**Steg:**
1. Installera appen från TestFlight
2. Öppna appen
3. Godkänn behörigheter (kamera, plats)
4. Skapa konto med e-post
5. Verifiera e-post
6. Slutför onboarding
**Förväntat resultat:**
- Appen startar utan krasch
- Behörighetsdialoger visas med tydlig text
- Konto skapas utan fel
- E-postverifiering fungerar
- Användaren kommer till huvudskärmen
**Att kontrollera:**
- [ ] Tid till första skärm < 3 sekunder
- [ ] Alla behörighetsbeskrivningar är tydliga
- [ ] Ingen data förloras vid avbrott
- [ ] Felmeddelanden är användarvänliga
---
### Scenario 2: Kamera & Foto ⭐ KRITISK
**Syfte:** Verifiera kamerafunktionalitet
**Steg:**
1. Gå till Capture-fliken
2. Ge kamerabehörighet
3. Vänta på kameraförhandsvisning
4. Tryck på shutter-knapp
5. Granska taget foto
6. Lägg till kommentar
7. Spara/submit
**Förväntat resultat:**
- Kameran startar inom 2 sekunder
- Förhandsvisning är flytande (30fps+)
- Foto sparas med korrekt metadata
- GPS-koordinater inkluderas
**Att kontrollera:**
- [ ] Foto-kvalitet är acceptabel
- [ ] GPS-taggning är korrekt (±10m)
- [ ] Tidsstämpel är korrekt
- [ ] Flash fungerar (auto/on/off)
- [ ] Zoom fungerar smidigt
---
### Scenario 3: GPS & Plats ⭐ KRITISK
**Syfte:** Verifiera GPS-funktionalitet
**Steg:**
1. Gå utomhus med GPS-täckning
2. Öppna appen
3. Kontrollera platsindikator
4. Gå 50 meter
5. Verifiera uppdaterad position
**Förväntat resultat:**
- Plats hämtas inom 5 sekunder
- Position uppdateras vid förflyttning
- Accuracy visas tydligt
**Att kontrollera:**
- [ ] GPS fungerar utomhus
- [ ] Plats uppdateras i realtid
- [ ] Accuracy är < 20 meter
- [ ] Bakgrundsspårning fungerar (om aktiverat)
---
### Scenario 4: Uppdrag & Submission ⭐ KRITISK
**Syfte:** Verifiera uppdragsflödet
**Steg:**
1. Visa lista med uppdrag
2. Välj och acceptera uppdrag
3. Följ instruktioner
4. Ta nödvändiga foton
5. Submit observation
**Förväntat resultat:**
- Uppdrag visas med korrekt info
- Accepterande fungerar
- Submission sparas
- Belöning visas
**Att kontrollera:**
- [ ] Uppdrag visas korrekt
- [ ] Submission valideras
- [ ] Belöning beräknas korrekt
- [ ] Status uppdateras i realtid
---
### Scenario 5: Offline-läge ⭐ KRITISK
**Syfte:** Verifiera offline-funktionalitet
**Steg:**
1. Aktivera flygplansläge
2. Ta foto
3. Försök submit
4. Stäng av flygplansläge
5. Vänta på synk
**Förväntat resultat:**
- Appen fungerar i offline-läge
- Foto sparas lokalt
- Submission köas
- Synk sker automatiskt vid återanslutning
**Att kontrollera:**
- [ ] Offline-indikator visas
- [ ] Köade items visas tydligt
- [ ] Synk sker automatiskt
- [ ] Ingen data förloras
- [ ] Retry-logik fungerar
---
### Scenario 6: Profil & Statistik
**Syfte:** Verifiera profil och stats
**Steg:**
1. Gå till Stats-fliken
2. Visa statistik
3. Gå till Profile-fliken
4. Uppdatera inställningar
5. Visa utbetalningsinfo
**Förväntat resultat:**
- Statistik visas korrekt
- Inställningar sparas
- Utbetalningsinfo är korrekt
---
### Scenario 7: Prestanda & Batteri
**Syfte:** Verifiera appens resursanvändning
**Steg:**
1. Använd appen i 30 minuter
2. Kontrollera batterianvändning
3. Kontrollera minnesanvändning
4. Testa med låg batterinivå
**Förväntat resultat:**
- Batterianvändning < 10% per timme
- Appen kraschar inte vid lågt batteri
- Minnesanvändning är stabil
---
## 🐛 Buggrapportering
### Allvarlighetsgrader
| Nivå | Beskrivning | Exempel |
|------|-------------|---------|
| 🔴 Kritisk | Appen kraschar eller data förloras | Krasch vid foto-tagning |
| 🟡 Hög | Funktion fungerar inte | GPS uppdateras inte |
| 🟢 Medel | UI-problem eller workaround finns | Knapp svår att trycka |
| ⚪ Låg | Kosmetiskt | Felaktig färg |
### Rapportmall
```
**Enhet:** iPhone 15 Pro, iOS 17.5
**Version:** 1.0.0 (Build 1)
**Allvarlighet:** 🔴 Kritisk
**Beskrivning:**
Appen kraschar när jag tar foto i mörker.
**Steg att reproducera:**
1. Gå till Capture-fliken
2. Stäng av ljuset
3. Tryck på shutter
**Förväntat:** Foto tas med flash
**Faktiskt:** Appen kraschar
**Skärmdump:** [bifogad]
**Kraschlogg:** [bifogad]
```
---
## 📊 Feedback-insamling
### Automatisk data (TestFlight)
- [ ] Antal installationer
- [ ] Sessionslängd
- [ ] Kraschrapporter
- [ ] Prestandametrics
### Manuell feedback
- [ ] Skärmdumpar med annotationer
- [ ] Skriftlig feedback via TestFlight
- [ ] Enkät efter testperiod
---
## 📅 Tidsplan
| Vecka | Aktivitet | Ansvarig |
|-------|-----------|----------|
| 1 | Installation & onboarding-test | Alla |
| 1 | Core-funktionalitetstest | Grupp A |
| 2 | Fälttest (utomhus) | Grupp A |
| 2 | UX-feedback | Grupp B |
| 2 | Prestandatest | Grupp C |
| 2 | Sammanställning & åtgärder | dev@quixzoom.com |
---
## ✅ Godkännandekriterier
För att godkänna beta-versionen:
- [ ] Inga kritiska buggar
- [ ] < 5 högprioriterade buggar
- [ ] Appen kraschar < 1% av sessioner
- [ ] Genomsnittlig rating ≥ 4/5
- [ ] Alla core-scenarier fungerar
- [ ] Offline-läge är stabilt
- [ ] Batterianvändning är acceptabel
---
*Senast uppdaterad: 2026-06-27*
-126
View File
@@ -1,126 +0,0 @@
apply plugin: "com.android.application"
apply plugin: "com.facebook.react"
import com.android.build.OutputFile
/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
*/
react {
/* Folders */
// The root of your project, i.e. where "package.json" lives. Default is '..'
// root = file("../")
// The folder where the react-native Codegen package is. Default is ../node_modules/@react-native/codegen
// codegenDir = file("../node_modules/@react-native/codegen")
// The cli.js file which is the React Native CLI entrypoint. Default is ../node_modules/react-native/cli.js
// cliFile = file("../node_modules/react-native/cli.js")
/* Variants */
// The list of variants to that are debuggable. For those we're going to
// skip the bundling of the JS bundle and the assets. By default is just 'debug'.
// If you add flavors like lite, prod, etc. you'll have to list your debuggableVariants.
// debuggableVariants = ["liteDebug", "prodDebug"]
/* Bundling */
// A list containing the node command and its flags. Default is just 'node'.
// nodeExecutableAndArgs = ["node"]
//
// The command to run when bundling. By default is 'bundle'
// bundleCommand = "ram-bundle"
//
// The path to the CLI configuration file. Default is empty.
// bundleConfig = file(../rn-cli.config.js)
//
// The name of the generated asset file containing your JS bundle
// bundleAssetName = "MyApplication.android.bundle"
//
// The entry file for bundle generation. Default is 'index.android.js' or 'index.js'
// entryFile = file("../js/MyApplication.android.js")
//
// A list of extra flags to pass to the 'bundle' commands.
// See https://github.com/react-native-community/cli/blob/main/docs/commands.md#bundle
// extraPackagerArgs = []
/* Hermes Commands */
// The hermes compiler command to run. By default it is 'hermesc'
// hermesCommand = "$rootDir/my-custom-hermesc/bin/hermesc"
//
// The list of flags to pass to the Hermes compiler. By default is "-O", "-output-source-map"
// hermesFlags = ["-O", "-output-source-map"]
}
/**
* Set this to true to Run Proguard on Release builds to minify the Java bytecode.
*/
def enableProguardInReleaseBuilds = false
/**
* The preferred build flavor of JavaScriptCore (JSC)
*
* For example, to use the international variant, you can use:
* `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
*
* The international variant includes ICU i18n library and necessary data
* allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
* give correct results when using with locales other than en-US. Note that
* this variant is about 6MiB larger per architecture than default.
*/
def jscFlavor = 'org.webkit:android-jsc:+'
android {
ndkVersion rootProject.ext.ndkVersion
compileSdkVersion rootProject.ext.compileSdkVersion
namespace "com.quixzoom"
defaultConfig {
applicationId "com.quixzoom"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0.0"
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://reactnative.dev/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
// The version of react-native is set by the React Native Gradle Plugin
implementation("com.facebook.react:react-android")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.0.0")
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}")
debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
exclude group:'com.squareup.okhttp3', module:'okhttp'
}
debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}")
if (hermesEnabled.toBoolean()) {
implementation("com.facebook.react:hermes-android")
} else {
implementation jscFlavor
}
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
@@ -1,38 +0,0 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature android:name="android.hardware.camera" android:required="true" />
<uses-feature android:name="android.hardware.location.gps" android:required="true" />
<application
android:name=".MainApplication"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="false"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
-43
View File
@@ -1,43 +0,0 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
ext {
buildToolsVersion = "33.0.0"
minSdkVersion = 21
compileSdkVersion = 33
targetSdkVersion = 33
ndkVersion = "23.1.7779620"
kotlinVersion = "1.8.0"
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath("com.android.tools.build:gradle:7.3.1")
classpath("com.facebook.react:react-native-gradle-plugin")
classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
}
}
allprojects {
repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url("$rootDir/../node_modules/react-native/android")
}
maven {
// Android JSC is installed from npm
url("$rootDir/../node_modules/jsc-android/dist")
}
mavenCentral {
// We don't want to fetch react-native from Maven Central as there are
// older versions over there.
content {
excludeGroup "com.facebook.react"
}
}
google()
maven { url 'https://www.jitpack.io' }
}
}
+57 -2
View File
@@ -1,4 +1,59 @@
{
"name": "quixzoom",
"displayName": "quiXzoom"
"expo": {
"name": "quiXzoom",
"slug": "quixzoom",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "dark",
"splash": {
"image": "./assets/splash-icon.png",
"resizeMode": "contain",
"backgroundColor": "#0A0A1B"
},
"scheme": "quixzoom",
"ios": {
"supportsTablet": true,
"bundleIdentifier": "com.wavult.quixzoom",
"infoPlist": {
"NSCameraUsageDescription": "quiXzoom behöver kameran för att ta uppdragsbilder",
"NSLocationWhenInUseUsageDescription": "quiXzoom behöver din position för att visa uppdrag nära dig och navigera till uppdragsplatser",
"NSLocationAlwaysAndWhenInUseUsageDescription": "quiXzoom behöver din position i bakgrunden för att fortsätta spåra uppdrag även när appen är i bakgrunden",
"NSLocationAlwaysUsageDescription": "quiXzoom behöver din position för att notifiera om uppdrag nära dig",
"UIBackgroundModes": ["location", "remote-notification"]
}
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#0A0A1B"
},
"package": "com.wavult.quixzoom",
"permissions": [
"android.permission.CAMERA",
"android.permission.ACCESS_FINE_LOCATION",
"android.permission.ACCESS_COARSE_LOCATION",
"android.permission.ACCESS_BACKGROUND_LOCATION",
"android.permission.FOREGROUND_SERVICE",
"android.permission.RECEIVE_BOOT_COMPLETED",
"android.permission.POST_NOTIFICATIONS"
]
},
"web": {
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-camera",
"expo-location",
"expo-secure-store",
[
"expo-notifications",
{
"icon": "./assets/notification-icon.png",
"color": "#6366f1",
"sounds": ["./assets/notification-sound.wav"]
}
]
]
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

-23
View File
@@ -1,23 +0,0 @@
/**
* quiXzoom App Entry Point
*/
import { AppRegistry } from 'react-native';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './src/store/store';
import AppNavigator from './src/navigation/AppNavigator';
import { name as appName } from './app.json';
// Root component with Redux and persistence
function Root() {
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<AppNavigator />
</PersistGate>
</Provider>
);
}
AppRegistry.registerComponent(appName, () => Root);
+8
View File
@@ -0,0 +1,8 @@
import { registerRootComponent } from 'expo';
import App from './App';
// registerRootComponent calls AppRegistry.registerComponent('main', () => App);
// It also ensures that whether you load the app in Expo Go or in a native build,
// the environment is set up appropriately
registerRootComponent(App);
-29
View File
@@ -1,29 +0,0 @@
<?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>method</key>
<string>app-store</string>
<key>teamID</key>
<string>YOUR_TEAM_ID</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
<key>compileBitcode</key>
<false/>
<key>provisioningProfiles</key>
<dict>
<key>com.quixzoom.app</key>
<string>quiXzoom App Store Distribution</string>
</dict>
<key>signingCertificate</key>
<string>iPhone Distribution</string>
<key>signingStyle</key>
<string>manual</string>
<key>stripSwiftSymbols</key>
<true/>
<key>thinning</key>
<string>&lt;none&gt;</string>
</dict>
</plist>
-65
View File
@@ -1,65 +0,0 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>quiXzoom</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.quixzoom.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>quiXzoom needs camera access to capture infrastructure observations</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>quiXzoom needs location access to tag observations with GPS coordinates</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>quiXzoom needs background location for mission tracking</string>
<key>NSMicrophoneUsageDescription</key>
<string>quiXzoom needs microphone for video observations</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>quiXzoom needs photo library access to upload existing images</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>gps</string>
<string>camera</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
-57
View File
@@ -1,57 +0,0 @@
require_relative '../node_modules/react-native/scripts/react_native_pods'
require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
platform :ios, '13.0'
prepare_react_native_project!
# If you are using a `react-native-flipper` your iOS build will fail when `NO_FLIPPER=1` is set.
# because `react-native-flipper` depends on (FlipperKit,...) that will be excluded
#
# To fix this you can also exclude `react-native-flipper` using a `react-native.config.js`
# ```js
# module.exports = {
# dependencies: {
# ...(process.env.NO_FLIPPER ? { 'react-native-flipper': { platforms: { ios: null } } } : {}),
# ```
flipper_config = ENV['NO_FLIPPER'] == "1" ? FlipperConfiguration.disabled : FlipperConfiguration.enabled
linkage = ENV['USE_FRAMEWORKS']
if linkage != nil
Pod::UI.puts "Configuring Pod with #{linkage}ally linked Frameworks".green
use_frameworks! :linkage => linkage.to_sym
end
target 'quixzoom' do
config = use_native_modules!
# Flags change depending on the env values.
flags = get_default_flags()
use_react_native!(
:path => config[:reactNativePath],
# Hermes is now enabled by default. Disable by setting this flag to false.
:hermes_enabled => flags[:hermes_enabled],
:fabric_enabled => flags[:fabric_enabled],
# Enables Flipper.
#
# Note that if you have use_frameworks! enabled, Flipper will not work and
# you should disable the next line.
:flipper_configuration => flipper_config,
# An absolute path to your application root.
:app_path => "#{Pod::Config.instance.installation_root}/.."
)
target 'quixzoomTests' do
inherit! :complete
# Pods for testing
end
post_install do |installer|
react_native_post_install(
installer,
config[:reactNativePath],
:mac_catalyst_enabled => false
)
__apply_Xcode_12_5_M1_post_install_workaround(installer)
end
end
@@ -1,386 +0,0 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB01A68108700A75B9A /* AppDelegate.mm */; };
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 13B07FB51A68108700A75B9A /* Images.xcassets */; };
13B07FC11A68108700A75B9A /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 13B07FB71A68108700A75B9A /* main.m */; };
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
13B07F961A680F5C00A75B9A /* quixzoom.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = quixzoom.app; sourceTree = BUILT_PRODUCTS_DIR; };
13B07FAF1A68108700A75B9A /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AppDelegate.h; path = quixzoom/AppDelegate.h; sourceTree = "<group>"; };
13B07FB01A68108700A75B9A /* AppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = AppDelegate.mm; path = quixzoom/AppDelegate.mm; sourceTree = "<group>"; };
13B07FB51A68108700A75B9A /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = quixzoom/Images.xcassets; sourceTree = "<group>"; };
13B07FB61A68108700A75B9A /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = quixzoom/Info.plist; sourceTree = "<group>"; };
13B07FB71A68108700A75B9A /* main.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = main.m; path = quixzoom/main.m; sourceTree = "<group>"; };
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = quixzoom/LaunchScreen.storyboard; sourceTree = "<group>"; };
ED297162215061F000B7C4FE /* JavaScriptCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = JavaScriptCore.framework; path = System/Library/Frameworks/JavaScriptCore.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
13B07F8C1A680F5C00A75B9A /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
13B07FAE1A68108700A75B9A /* quixzoom */ = {
isa = PBXGroup;
children = (
13B07FAF1A68108700A75B9A /* AppDelegate.h */,
13B07FB01A68108700A75B9A /* AppDelegate.mm */,
13B07FB51A68108700A75B9A /* Images.xcassets */,
13B07FB61A68108700A75B9A /* Info.plist */,
81AB9BB72411601600AC10FF /* LaunchScreen.storyboard */,
13B07FB71A68108700A75B9A /* main.m */,
);
name = quixzoom;
sourceTree = "<group>";
};
832341AE1AAA6A7D00B99B32 /* Libraries */ = {
isa = PBXGroup;
children = (
);
name = Libraries;
sourceTree = "<group>";
};
83CBB9F61A601CBA00E9B192 = {
isa = PBXGroup;
children = (
13B07FAE1A68108700A75B9A /* quixzoom */,
832341AE1AAA6A7D00B99B32 /* Libraries */,
83CBBA001A601CBA00E9B192 /* Products */,
2D16E6871FA4F8E400B85C8A /* Frameworks */,
);
indentWidth = 2;
sourceTree = "<group>";
tabWidth = 2;
usesTabs = 0;
};
83CBBA001A601CBA00E9B192 /* Products */ = {
isa = PBXGroup;
children = (
13B07F961A680F5C00A75B9A /* quixzoom.app */,
);
name = Products;
sourceTree = "<group>";
};
2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
isa = PBXGroup;
children = (
ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
);
name = Frameworks;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
13B07F861A680F5C00A75B9A /* quixzoom */ = {
isa = PBXNativeTarget;
buildConfigurationList = 13B07F931A680F5C00A75B9A /* Build configuration list for PBXNativeTarget "quixzoom" */;
buildPhases = (
FD10A7F022414F080027D42C /* Start Packager */,
13B07F871A680F5C00A75B9A /* Sources */,
13B07F8C1A680F5C00A75B9A /* Frameworks */,
13B07F8E1A680F5C00A75B9A /* Resources */,
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */,
);
buildRules = (
);
dependencies = (
);
name = quixzoom;
productName = quixzoom;
productReference = 13B07F961A680F5C00A75B9A /* quixzoom.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
83CBB9F71A601CBA00E9B192 /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1210;
TargetAttributes = {
13B07F861A680F5C00A75B9A = {
LastSwiftMigration = 1120;
};
};
};
buildConfigurationList = 83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "quixzoom" */;
compatibilityVersion = "Xcode 12.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 83CBB9F61A601CBA00E9B192;
productRefGroup = 83CBBA001A601CBA00E9B192 /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
13B07F861A680F5C00A75B9A /* quixzoom */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
13B07F8E1A680F5C00A75B9A /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
81AB9BB82411601600AC10FF /* LaunchScreen.storyboard in Resources */,
13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
00DD1BFF1BD5951E006B06BC /* Bundle React Native code and images */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Bundle React Native code and images";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "set -e\n\nWITH_ENVIRONMENT=\"../node_modules/react-native/scripts/xcode/with-environment.sh\"\nREACT_NATIVE_XCODE=\"../node_modules/react-native/scripts/react-native-xcode.sh\"\n\n/bin/sh -c \"$WITH_ENVIRONMENT $REACT_NATIVE_XCODE\"";
};
FD10A7F022414F080027D42C /* Start Packager */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Start Packager";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "export RCT_METRO_PORT=\"${RCT_METRO_PORT:=8081}\"\necho \"export RCT_METRO_PORT=${RCT_METRO_PORT}\" > \"${SRCROOT}/../node_modules/react-native/scripts/.packager.env\"\nif [ -z \"${RCT_NO_LAUNCH_PACKAGER+xxx}\" ] ; then\n if nc -w 5 -z localhost ${RCT_METRO_PORT} ; then\n if ! curl -s \"http://localhost:${RCT_METRO_PORT}/status\" | grep -q \"packager-status:running\" ; then\n echo \"Port ${RCT_METRO_PORT} already in use, packager is either not running or not running correctly\"\n exit 2\n fi\n else\n open \"$SRCROOT/../node_modules/react-native/scripts/launchPackager.command\" || echo \"Can't start packager automatically\"\n fi\nfi\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
13B07F871A680F5C00A75B9A /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
13B07FBC1A68108700A75B9A /* AppDelegate.mm in Sources */,
13B07FC11A68108700A75B9A /* main.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
13B07F941A680F5C00A75B9A /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5709B34CF0A7D63546082F79 /* Pods-quixzoom.debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = quixzoom/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.quixzoom.app;
PRODUCT_NAME = quixzoom;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
13B07F951A680F5C00A75B9A /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5709B34DF0A7D63546082F79 /* Pods-quixzoom.release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = 1;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = quixzoom/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
OTHER_LDFLAGS = (
"$(inherited)",
"-ObjC",
"-lc++",
);
PRODUCT_BUNDLE_IDENTIFIER = com.quixzoom.app;
PRODUCT_NAME = quixzoom;
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
83CBBA201A601CBA00E9B192 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
"ENABLE_TESTABILITY[sdk=*]" = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_SYMBOLS_PRIVATE_EXTERN = NO;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
};
name = Debug;
};
83CBBA211A601CBA00E9B192 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_CXX_LANGUAGE_STANDARD = "c++20";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution";
COPY_PHASE_STRIP = YES;
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
13B07F931A680F5C00A75B9A /* Build configuration list for PBXNativeTarget "quixzoom" */ = {
isa = XCConfigurationList;
buildConfigurations = (
13B07F941A680F5C00A75B9A /* Debug */,
13B07F951A680F5C00A75B9A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
83CBB9FA1A601CBA00E9B192 /* Build configuration list for PBXProject "quixzoom" */ = {
isa = XCConfigurationList;
buildConfigurations = (
83CBBA201A601CBA00E9B192 /* Debug */,
83CBBA211A601CBA00E9B192 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 83CBB9F71A601CBA00E9B192 /* Project object */;
}
@@ -1,5 +0,0 @@
#import <React/RCTAppDelegate.h>
@interface AppDelegate : RCTAppDelegate
@end
@@ -1,31 +0,0 @@
#import "AppDelegate.h"
#import <React/RCTBundleURLProvider.h>
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.moduleName = @"quixzoom";
// You can add your custom initial props in the dictionary below.
// They will be passed down to the ViewController used by React Native.
self.initialProps = @{};
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [self getBundleURL];
}
- (NSURL *)getBundleURL
{
#if DEBUG
return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
#else
return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
#endif
}
@end
@@ -1,62 +0,0 @@
{
"images" : [
{
"filename" : "Icon-Notification@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"filename" : "Icon-Notification@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"filename" : "Icon-Small@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"filename" : "Icon-Small@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"filename" : "Icon-Small-40@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-Small-40@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-60@3x.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"filename" : "Icon-1024.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -1,6 +0,0 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
-100
View File
@@ -1,100 +0,0 @@
<?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>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>quiXzoom</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
<key>NSAllowsLocalNetworking</key>
<true/>
<key>NSExceptionDomains</key>
<dict>
<key>localhost</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
<key>api.quixzoom.com</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<false/>
</dict>
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>quiXzoom needs camera access to capture infrastructure observations and field data. Photos are tagged with GPS coordinates for mission verification.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>quiXzoom needs location access to tag observations with precise GPS coordinates and show nearby missions.</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>quiXzoom uses background location for mission tracking and route optimization while you complete field observations.</string>
<key>NSMicrophoneUsageDescription</key>
<string>quiXzoom needs microphone access for video observations with audio annotations.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>quiXzoom needs photo library access to upload existing images for mission submissions.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>quiXzoom can save captured observations to your photo library for backup.</string>
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
<string>location</string>
<string>processing</string>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
<string>gps</string>
<string>camera</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.quixzoom.app</string>
<key>CFBundleURLSchemes</key>
<array>
<string>quixzoom</string>
</array>
</dict>
</array>
</dict>
</plist>
@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="21507" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="21505"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="quiXzoom" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="GJd-Yh-RWb">
<rect key="frame" x="0.0" y="263.66666666666669" width="393" height="43"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
<color key="textColor" red="0.18431372549019609" green="0.47843137254901963" blue="0.41176470588235292" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Field Data Collection" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="MN2-I3-ftu">
<rect key="frame" x="0.0" y="314.66666666666669" width="393" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.55294117647058827" green="0.55294117647058827" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<viewLayoutGuide key="safeArea" id="Bcu-3y-fUS"/>
<color key="backgroundColor" red="0.10980392156862745" green="0.10980392156862745" blue="0.11764705882352941" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="MN2-I3-ftu" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="Bcu-3y-fUS" firstAttribute="centerX" secondItem="GJd-Yh-RWb" secondAttribute="centerX" id="Q3B-4B-g5N"/>
<constraint firstItem="MN2-I3-ftu" firstAttribute="top" secondItem="GJd-Yh-RWb" secondAttribute="bottom" constant="8" symbolic="YES" id="Uz7-9L-2Ib"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" constant="-140.83333333333331" id="mWs-4c-Lg3"/>
<constraint firstItem="GJd-Yh-RWb" firstAttribute="leading" secondItem="Bcu-3y-fUS" secondAttribute="leading" symbolic="YES" id="x7j-FC-K8j"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="52.671755725190835" y="374.64788732394368"/>
</scene>
</scenes>
</document>
-10
View File
@@ -1,10 +0,0 @@
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char *argv[])
{
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@@ -1,19 +0,0 @@
<?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>aps-environment</key>
<string>production</string>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:quixzoom.com</string>
<string>applinks:app.quixzoom.com</string>
</array>
<key>com.apple.developer.networking.wifi-info</key>
<true/>
</dict>
</plist>
+341
View File
@@ -0,0 +1,341 @@
/**
* quiXzoom API client
* Auth: identity-core JWT (stored in SecureStore)
*/
import * as SecureStore from 'expo-secure-store'
const API_BASE = process.env.EXPO_PUBLIC_API_URL ?? 'https://api.quixzoom.com'
const TOKEN_KEY = 'qxz_jwt'
const REFRESH_KEY = 'qxz_refresh'
// ─── Token management ──────────────────────────────────────────────────────
export async function getToken(): Promise<string | null> {
return SecureStore.getItemAsync(TOKEN_KEY)
}
export async function setToken(token: string, refreshToken?: string): Promise<void> {
await SecureStore.setItemAsync(TOKEN_KEY, token)
if (refreshToken) await SecureStore.setItemAsync(REFRESH_KEY, refreshToken)
}
export async function clearTokens(): Promise<void> {
await SecureStore.deleteItemAsync(TOKEN_KEY)
await SecureStore.deleteItemAsync(REFRESH_KEY)
}
// ─── Base fetch with JWT ───────────────────────────────────────────────────
async function apiFetch<T>(
path: string,
options: RequestInit = {},
): Promise<T> {
const token = await getToken()
const headers: Record<string, string> = {
'Content-Type': 'application/json',
...(options.headers as Record<string, string>),
}
if (token) headers['Authorization'] = `Bearer ${token}`
const res = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (res.status === 401) {
// Try refresh
const refreshed = await attemptRefresh()
if (refreshed) {
headers['Authorization'] = `Bearer ${refreshed}`
const retry = await fetch(`${API_BASE}${path}`, { ...options, headers })
if (!retry.ok) throw new ApiError(retry.status, await retry.text())
return retry.json() as Promise<T>
}
throw new ApiError(401, 'Unauthorized')
}
if (!res.ok) throw new ApiError(res.status, await res.text())
return res.json() as Promise<T>
}
async function attemptRefresh(): Promise<string | null> {
const refresh = await SecureStore.getItemAsync(REFRESH_KEY)
if (!refresh) return null
try {
const res = await fetch(`${API_BASE}/auth/refresh`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: refresh }),
})
if (!res.ok) return null
const data = await res.json() as { access_token: string; refresh_token?: string }
await setToken(data.access_token, data.refresh_token)
return data.access_token
} catch {
return null
}
}
export class ApiError extends Error {
constructor(public status: number, message: string) {
super(message)
this.name = 'ApiError'
}
}
// ─── Types ─────────────────────────────────────────────────────────────────
export type MissionStatus = 'open' | 'active' | 'completed' | 'cancelled' | 'expired'
export type MissionCategory = 'infrastruktur' | 'vägar' | 'hamnar' | 'bryggor' | 'fritidshus' | 'miljö' | 'övrigt'
export interface Mission {
id: string
title: string
description: string
status: MissionStatus
category: MissionCategory
latitude: number
longitude: number
city: string
reward_amount: number // in öre (SEK * 100)
reward_currency: string // 'SEK'
deadline_at: string | null // ISO
created_at: string
distance_meters?: number // populated when fetching nearby
}
export interface MissionDetail extends Mission {
instructions: string
requirements: string[]
max_submissions: number
submission_count: number
}
export interface Submission {
id: string
mission_id: string
zoomer_id: string
status: 'pending' | 'approved' | 'rejected'
media_urls: string[]
created_at: string
reward_paid: number
}
export interface Zoomer {
id: string
email: string
display_name: string
avatar_url: string | null
level: number
total_earned: number // öre
missions_completed: number
badges: Badge[]
joined_at: string
}
export interface Badge {
id: string
label: string
emoji: string
earned_at: string
}
export interface EarningsDay {
date: string // YYYY-MM-DD
amount: number // öre
missions: number
}
export interface EarningsSummary {
today: number
this_week: number
total: number
pending: number
currency: string
}
export interface Payout {
id: string
amount: number
currency: string
status: 'pending' | 'approved' | 'paid' | 'rejected'
created_at: string
paid_at: string | null
}
export interface AuthResult {
access_token: string
refresh_token: string
zoomer: Zoomer
}
// ─── Auth ──────────────────────────────────────────────────────────────────
export const auth = {
async login(email: string, password: string): Promise<AuthResult> {
const data = await apiFetch<AuthResult>('/auth/login', {
method: 'POST',
body: JSON.stringify({ email, password }),
})
await setToken(data.access_token, data.refresh_token)
return data
},
async register(email: string, password: string, displayName?: string): Promise<AuthResult> {
const data = await apiFetch<AuthResult>('/auth/register', {
method: 'POST',
body: JSON.stringify({ email, password, display_name: displayName }),
})
await setToken(data.access_token, data.refresh_token)
return data
},
async logout(): Promise<void> {
try {
await apiFetch('/auth/logout', { method: 'POST' })
} catch {
// best-effort
}
await clearTokens()
},
async me(): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me')
},
}
// ─── Missions ──────────────────────────────────────────────────────────────
export const missions = {
/** Nearby open missions sorted by distance */
async nearby(lat: number, lng: number, radiusKm = 50): Promise<Mission[]> {
const q = new URLSearchParams({
lat: lat.toString(),
lng: lng.toString(),
radius_km: radiusKm.toString(),
status: 'open',
})
return apiFetch<Mission[]>(`/missions/near?${q}`)
},
/** List all open missions (paginated) */
async list(page = 1, category?: MissionCategory): Promise<Mission[]> {
const q = new URLSearchParams({ page: page.toString(), status: 'open' })
if (category) q.set('category', category)
return apiFetch<Mission[]>(`/missions?${q}`)
},
async get(id: string): Promise<MissionDetail> {
return apiFetch<MissionDetail>(`/missions/${id}`)
},
async accept(id: string): Promise<{ ok: boolean }> {
return apiFetch<{ ok: boolean }>(`/missions/${id}/claim`, { method: 'PATCH' })
},
async submit(id: string, mediaUris: string[]): Promise<Submission> {
const form = new FormData()
for (const uri of mediaUris) {
const filename = uri.split('/').pop() ?? 'image.jpg'
form.append('media', {
uri,
name: filename,
type: 'image/jpeg',
} as unknown as Blob)
}
const token = await getToken()
const res = await fetch(`${API_BASE}/missions/${id}/submit`, {
method: 'POST',
headers: token ? { Authorization: `Bearer ${token}` } : {},
body: form,
})
if (!res.ok) throw new ApiError(res.status, await res.text())
return res.json() as Promise<Submission>
},
/** My active/accepted missions */
async myActive(): Promise<Mission[]> {
return apiFetch<Mission[]>('/missions/claims/active')
},
}
// ─── Wallet / Earnings ─────────────────────────────────────────────────────
export interface WalletBalance {
balance: number // öre
currency: string
pending_payouts: number
}
export const wallet = {
async balance(): Promise<WalletBalance> {
return apiFetch<WalletBalance>('/auth/wallet')
},
}
export const payouts = {
async history(): Promise<Payout[]> {
return apiFetch<Payout[]>('/payouts/history')
},
async pending(): Promise<Payout[]> {
return apiFetch<Payout[]>('/payouts/pending')
},
async request(amount: number): Promise<Payout> {
return apiFetch<Payout>('/payouts/request', {
method: 'POST',
body: JSON.stringify({ amount }),
})
},
}
// ─── Passwordless Auth ───────────────────────────────────────────────────
export interface PasswordlessRequest {
request_id: string
request_token: string
status: 'pending' | 'scanned' | 'approved' | 'denied' | 'expired'
device_info?: {
type: string
name: string
browser?: string
}
created_at: string
expires_at: string
}
export const passwordless = {
/** Approve a passwordless login request */
async approve(requestToken: string): Promise<{ ok: boolean; zoomer?: Zoomer }> {
return apiFetch<{ ok: boolean; zoomer?: Zoomer }>('/auth/passwordless/approve', {
method: 'POST',
body: JSON.stringify({ request_token: requestToken }),
})
},
/** Deny a passwordless login request */
async deny(requestToken: string): Promise<{ ok: boolean }> {
return apiFetch<{ ok: boolean }>('/auth/passwordless/deny', {
method: 'POST',
body: JSON.stringify({ request_token: requestToken }),
})
},
/** Get pending auth requests for current user */
async pending(): Promise<PasswordlessRequest[]> {
return apiFetch<PasswordlessRequest[]>('/auth/passwordless/pending')
},
}
// ─── Profile ───────────────────────────────────────────────────────────────
export const profile = {
async get(): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me')
},
async update(data: { display_name?: string }): Promise<Zoomer> {
return apiFetch<Zoomer>('/auth/me', {
method: 'PATCH',
body: JSON.stringify(data),
})
},
}
+9056
View File
@@ -0,0 +1,9056 @@
{
"name": "quixzoom-mobile",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "quixzoom-mobile",
"version": "1.0.0",
"dependencies": {
"@react-navigation/bottom-tabs": "^7.15.7",
"@react-navigation/native": "^7.2.0",
"@react-navigation/native-stack": "^7.14.7",
"@supabase/supabase-js": "^2.100.0",
"@tanstack/react-query": "^5.95.2",
"expo": "~55.0.8",
"expo-camera": "~55.0.10",
"expo-image-picker": "~55.0.13",
"expo-linking": "~7.1.4",
"expo-location": "~55.1.4",
"expo-notifications": "^0.30.7",
"expo-secure-store": "~55.0.9",
"expo-status-bar": "~55.0.4",
"react": "19.2.0",
"react-native": "0.83.2",
"react-native-gesture-handler": "~2.30.0",
"react-native-maps": "^1.27.2",
"react-native-safe-area-context": "^5.7.0",
"react-native-screens": "~4.23.0",
"zustand": "^5.0.12"
},
"devDependencies": {
"@types/react": "~19.2.2",
"typescript": "~5.9.2"
}
},
"node_modules/@babel/code-frame": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
"integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.28.5",
"js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/compat-data": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
"integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/core": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
"integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helpers": "^7.28.6",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/remapping": "^2.3.5",
"convert-source-map": "^2.0.0",
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.2",
"json5": "^2.2.3",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/babel"
}
},
"node_modules/@babel/core/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/generator": {
"version": "7.29.1",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
"integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.29.0",
"@babel/types": "^7.29.0",
"@jridgewell/gen-mapping": "^0.3.12",
"@jridgewell/trace-mapping": "^0.3.28",
"jsesc": "^3.0.2"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-annotate-as-pure": {
"version": "7.27.3",
"resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.27.3.tgz",
"integrity": "sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.3"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
"integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-validator-option": "^7.27.1",
"browserslist": "^4.24.0",
"lru-cache": "^5.1.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-compilation-targets/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-create-class-features-plugin": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.28.6.tgz",
"integrity": "sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/helper-replace-supers": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/traverse": "^7.28.6",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-create-class-features-plugin/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-create-regexp-features-plugin": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.28.5.tgz",
"integrity": "sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"regexpu-core": "^6.3.1",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-create-regexp-features-plugin/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/helper-define-polyfill-provider": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz",
"integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"debug": "^4.4.3",
"lodash.debounce": "^4.0.8",
"resolve": "^1.22.11"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/@babel/helper-globals": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
"integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-member-expression-to-functions": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.28.5.tgz",
"integrity": "sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.28.5",
"@babel/types": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-imports": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
"integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-module-transforms": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
"integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-validator-identifier": "^7.28.5",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-optimise-call-expression": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.27.1.tgz",
"integrity": "sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-plugin-utils": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
"integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-remap-async-to-generator": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.27.1.tgz",
"integrity": "sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-wrap-function": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-replace-supers": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.28.6.tgz",
"integrity": "sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==",
"license": "MIT",
"dependencies": {
"@babel/helper-member-expression-to-functions": "^7.28.5",
"@babel/helper-optimise-call-expression": "^7.27.1",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/helper-skip-transparent-expression-wrappers": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.27.1.tgz",
"integrity": "sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.27.1",
"@babel/types": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-string-parser": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-identifier": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-validator-option": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
"integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helper-wrap-function": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.28.6.tgz",
"integrity": "sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/traverse": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/helpers": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
"integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.25.9",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.9.tgz",
"integrity": "sha512-llL88JShoCsth8fF8R4SJnIn+WLvR6ccFxu1H3FlMhDontdcmZWf2HgIZ7AIqV3Xcck1idlohrN4EUBQz6klbw==",
"license": "MIT",
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.9",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/@babel/highlight/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/@babel/highlight/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@babel/highlight/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/highlight/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/@babel/parser": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
"integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.29.0"
},
"bin": {
"parser": "bin/babel-parser.js"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@babel/plugin-proposal-decorators": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.0.tgz",
"integrity": "sha512-CVBVv3VY/XRMxRYq5dwr2DS7/MvqPm23cOCjbwNnVrfOqcWlnefua1uUs0sjdKOGjvPUG633o07uWzJq4oI6dA==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-syntax-decorators": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-proposal-export-default-from": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-default-from/-/plugin-proposal-export-default-from-7.27.1.tgz",
"integrity": "sha512-hjlsMBl1aJc5lp8MoCDEZCiYzlgdRAShOjAfRw6X+GlpLpUPU7c3XNLsKFZbQk/1cRzBlJ7CXg3xJAJMrFa1Uw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-async-generators": {
"version": "7.8.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz",
"integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-bigint": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz",
"integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-class-properties": {
"version": "7.12.13",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz",
"integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.12.13"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-class-static-block": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz",
"integrity": "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-decorators": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.28.6.tgz",
"integrity": "sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-dynamic-import": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz",
"integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-export-default-from": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-default-from/-/plugin-syntax-export-default-from-7.28.6.tgz",
"integrity": "sha512-Svlx1fjJFnNz0LZeUaybRukSxZI3KkpApUmIRzEdXC5k8ErTOz0OD0kNrICi5Vc3GlpP5ZCeRyRO+mfWTSz+iQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-flow": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-flow/-/plugin-syntax-flow-7.28.6.tgz",
"integrity": "sha512-D+OrJumc9McXNEBI/JmFnc/0uCM2/Y3PEBG3gfV3QIYkKv5pvnpzFrl1kYCrcHJP8nOeFB/SHi1IHz29pNGuew==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-attributes": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.28.6.tgz",
"integrity": "sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-import-meta": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz",
"integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-json-strings": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz",
"integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-jsx": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.28.6.tgz",
"integrity": "sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-logical-assignment-operators": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz",
"integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-nullish-coalescing-operator": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz",
"integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-numeric-separator": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz",
"integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.10.4"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-object-rest-spread": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz",
"integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-optional-catch-binding": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz",
"integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-optional-chaining": {
"version": "7.8.3",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz",
"integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.8.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-private-property-in-object": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz",
"integrity": "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-top-level-await": {
"version": "7.14.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz",
"integrity": "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.14.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-syntax-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.28.6.tgz",
"integrity": "sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-arrow-functions": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.27.1.tgz",
"integrity": "sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-async-generator-functions": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.0.tgz",
"integrity": "sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1",
"@babel/traverse": "^7.29.0"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-async-to-generator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.28.6.tgz",
"integrity": "sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-remap-async-to-generator": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-block-scoping": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.28.6.tgz",
"integrity": "sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-class-properties": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.28.6.tgz",
"integrity": "sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-class-static-block": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.28.6.tgz",
"integrity": "sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.12.0"
}
},
"node_modules/@babel/plugin-transform-classes": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.6.tgz",
"integrity": "sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-globals": "^7.28.0",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-replace-supers": "^7.28.6",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-computed-properties": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.28.6.tgz",
"integrity": "sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/template": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-destructuring": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.28.5.tgz",
"integrity": "sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-export-namespace-from": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.27.1.tgz",
"integrity": "sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-flow-strip-types": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-flow-strip-types/-/plugin-transform-flow-strip-types-7.27.1.tgz",
"integrity": "sha512-G5eDKsu50udECw7DL2AcsysXiQyB7Nfg521t2OAJ4tbfTJ27doHLeF/vlI1NZGlLdbb/v+ibvtL1YBQqYOwJGg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/plugin-syntax-flow": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-for-of": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.27.1.tgz",
"integrity": "sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-function-name": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.27.1.tgz",
"integrity": "sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/traverse": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-literals": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.27.1.tgz",
"integrity": "sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-logical-assignment-operators": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.28.6.tgz",
"integrity": "sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.28.6.tgz",
"integrity": "sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-transforms": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-named-capturing-groups-regex": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.0.tgz",
"integrity": "sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.28.5",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/@babel/plugin-transform-nullish-coalescing-operator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.28.6.tgz",
"integrity": "sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-numeric-separator": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.28.6.tgz",
"integrity": "sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-object-rest-spread": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.28.6.tgz",
"integrity": "sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==",
"license": "MIT",
"dependencies": {
"@babel/helper-compilation-targets": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-transform-destructuring": "^7.28.5",
"@babel/plugin-transform-parameters": "^7.27.7",
"@babel/traverse": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-optional-catch-binding": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.28.6.tgz",
"integrity": "sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-optional-chaining": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.28.6.tgz",
"integrity": "sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-parameters": {
"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"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-private-methods": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.28.6.tgz",
"integrity": "sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-private-property-in-object": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.28.6.tgz",
"integrity": "sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-display-name": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.28.0.tgz",
"integrity": "sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.28.6.tgz",
"integrity": "sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/plugin-syntax-jsx": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-development": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.27.1.tgz",
"integrity": "sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==",
"license": "MIT",
"dependencies": {
"@babel/plugin-transform-react-jsx": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-self": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
"integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-jsx-source": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
"integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-react-pure-annotations": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.27.1.tgz",
"integrity": "sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-regenerator": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.0.tgz",
"integrity": "sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-runtime": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.29.0.tgz",
"integrity": "sha512-jlaRT5dJtMaMCV6fAuLbsQMSwz/QkvaHOHOSXRitGGwSpR1blCY4KUKoyP2tYO8vJcqYe8cEj96cqSztv3uF9w==",
"license": "MIT",
"dependencies": {
"@babel/helper-module-imports": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"babel-plugin-polyfill-corejs2": "^0.4.14",
"babel-plugin-polyfill-corejs3": "^0.13.0",
"babel-plugin-polyfill-regenerator": "^0.6.5",
"semver": "^6.3.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-runtime/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/@babel/plugin-transform-shorthand-properties": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.27.1.tgz",
"integrity": "sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-spread": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.28.6.tgz",
"integrity": "sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-sticky-regex": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.27.1.tgz",
"integrity": "sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-typescript": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.28.6.tgz",
"integrity": "sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==",
"license": "MIT",
"dependencies": {
"@babel/helper-annotate-as-pure": "^7.27.3",
"@babel/helper-create-class-features-plugin": "^7.28.6",
"@babel/helper-plugin-utils": "^7.28.6",
"@babel/helper-skip-transparent-expression-wrappers": "^7.27.1",
"@babel/plugin-syntax-typescript": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/plugin-transform-unicode-regex": {
"version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.27.1.tgz",
"integrity": "sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==",
"license": "MIT",
"dependencies": {
"@babel/helper-create-regexp-features-plugin": "^7.27.1",
"@babel/helper-plugin-utils": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-react": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-react/-/preset-react-7.28.5.tgz",
"integrity": "sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-transform-react-display-name": "^7.28.0",
"@babel/plugin-transform-react-jsx": "^7.27.1",
"@babel/plugin-transform-react-jsx-development": "^7.27.1",
"@babel/plugin-transform-react-pure-annotations": "^7.27.1"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/preset-typescript": {
"version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.28.5.tgz",
"integrity": "sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==",
"license": "MIT",
"dependencies": {
"@babel/helper-plugin-utils": "^7.27.1",
"@babel/helper-validator-option": "^7.27.1",
"@babel/plugin-syntax-jsx": "^7.27.1",
"@babel/plugin-transform-modules-commonjs": "^7.27.1",
"@babel/plugin-transform-typescript": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0-0"
}
},
"node_modules/@babel/runtime": {
"version": "7.29.2",
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
"integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/template": {
"version": "7.28.6",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
"integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/parser": "^7.28.6",
"@babel/types": "^7.28.6"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/traverse--for-generate-function-map": {
"name": "@babel/traverse",
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
"integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.29.0",
"@babel/generator": "^7.29.0",
"@babel/helper-globals": "^7.28.0",
"@babel/parser": "^7.29.0",
"@babel/template": "^7.28.6",
"@babel/types": "^7.29.0",
"debug": "^4.3.1"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/types": {
"version": "7.29.0",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
"integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
"license": "MIT",
"dependencies": {
"@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.28.5"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@egjs/hammerjs": {
"version": "2.0.17",
"resolved": "https://registry.npmjs.org/@egjs/hammerjs/-/hammerjs-2.0.17.tgz",
"integrity": "sha512-XQsZgjm2EcVUiZQf11UBJQfmZeEmOW8DpI1gsFeln6w0ae0ii4dMQEQ0kjl6DspdWX1aGY1/loyXnP0JS06e/A==",
"license": "MIT",
"dependencies": {
"@types/hammerjs": "^2.0.36"
},
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/@expo/code-signing-certificates": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@expo/code-signing-certificates/-/code-signing-certificates-0.0.6.tgz",
"integrity": "sha512-iNe0puxwBNEcuua9gmTGzq+SuMDa0iATai1FlFTMHJ/vUmKvN/V//drXoLJkVb5i5H3iE/n/qIJxyoBnXouD0w==",
"license": "MIT",
"dependencies": {
"node-forge": "^1.3.3"
}
},
"node_modules/@expo/config": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.10.tgz",
"integrity": "sha512-qCHxo9H1ZoeW+y0QeMtVZ3JfGmumpGrgUFX60wLWMarraoQZSe47ZUm9kJSn3iyoPjUtUNanO3eXQg+K8k4rag==",
"license": "MIT",
"dependencies": {
"@expo/config-plugins": "~55.0.7",
"@expo/config-types": "^55.0.5",
"@expo/json-file": "^10.0.12",
"@expo/require-utils": "^55.0.3",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-from": "^5.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4"
}
},
"node_modules/@expo/config-plugins": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.7.tgz",
"integrity": "sha512-XZUoDWrsHEkH3yasnDSJABM/UxP5a1ixzRwU/M+BToyn/f0nTrSJJe/Ay/FpxkI4JSNz2n0e06I23b2bleXKVA==",
"license": "MIT",
"dependencies": {
"@expo/config-types": "^55.0.5",
"@expo/json-file": "~10.0.12",
"@expo/plist": "^0.5.2",
"@expo/sdk-runtime-versions": "^1.0.0",
"chalk": "^4.1.2",
"debug": "^4.3.5",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"resolve-from": "^5.0.0",
"semver": "^7.5.4",
"slugify": "^1.6.6",
"xcode": "^3.0.1",
"xml2js": "0.6.0"
}
},
"node_modules/@expo/config-types": {
"version": "55.0.5",
"resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.5.tgz",
"integrity": "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==",
"license": "MIT"
},
"node_modules/@expo/devcert": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/@expo/devcert/-/devcert-1.2.1.tgz",
"integrity": "sha512-qC4eaxmKMTmJC2ahwyui6ud8f3W60Ss7pMkpBq40Hu3zyiAaugPXnZ24145U7K36qO9UHdZUVxsCvIpz2RYYCA==",
"license": "MIT",
"dependencies": {
"@expo/sudo-prompt": "^9.3.1",
"debug": "^3.1.0"
}
},
"node_modules/@expo/devcert/node_modules/debug": {
"version": "3.2.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
"integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.1"
}
},
"node_modules/@expo/devtools": {
"version": "55.0.2",
"resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-55.0.2.tgz",
"integrity": "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==",
"license": "MIT",
"dependencies": {
"chalk": "^4.1.2"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/@expo/env": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-2.1.1.tgz",
"integrity": "sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==",
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"debug": "^4.3.4",
"getenv": "^2.0.0"
},
"engines": {
"node": ">=20.12.0"
}
},
"node_modules/@expo/fingerprint": {
"version": "0.16.6",
"resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.6.tgz",
"integrity": "sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==",
"license": "MIT",
"dependencies": {
"@expo/env": "^2.0.11",
"@expo/spawn-async": "^1.7.2",
"arg": "^5.0.2",
"chalk": "^4.1.2",
"debug": "^4.3.4",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"ignore": "^5.3.1",
"minimatch": "^10.2.2",
"resolve-from": "^5.0.0",
"semver": "^7.6.0"
},
"bin": {
"fingerprint": "bin/cli.js"
}
},
"node_modules/@expo/image-utils": {
"version": "0.8.12",
"resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.12.tgz",
"integrity": "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"getenv": "^2.0.0",
"jimp-compact": "0.16.1",
"parse-png": "^2.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.6.0"
}
},
"node_modules/@expo/json-file": {
"version": "10.0.12",
"resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz",
"integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"json5": "^2.2.3"
}
},
"node_modules/@expo/local-build-cache-provider": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.7.tgz",
"integrity": "sha512-Qg9uNZn1buv4zJUA4ZQaz+ZnKDCipRgjoEg2Gcp8Qfy+2Gq5yZKX4YN1TThCJ01LJk/pvJsCRxXlXZSwdZppgg==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"chalk": "^4.1.2"
}
},
"node_modules/@expo/metro": {
"version": "54.2.0",
"resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz",
"integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==",
"license": "MIT",
"dependencies": {
"metro": "0.83.3",
"metro-babel-transformer": "0.83.3",
"metro-cache": "0.83.3",
"metro-cache-key": "0.83.3",
"metro-config": "0.83.3",
"metro-core": "0.83.3",
"metro-file-map": "0.83.3",
"metro-minify-terser": "0.83.3",
"metro-resolver": "0.83.3",
"metro-runtime": "0.83.3",
"metro-source-map": "0.83.3",
"metro-symbolicate": "0.83.3",
"metro-transform-plugins": "0.83.3",
"metro-transform-worker": "0.83.3"
}
},
"node_modules/@expo/osascript": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/@expo/osascript/-/osascript-2.4.2.tgz",
"integrity": "sha512-/XP7PSYF2hzOZzqfjgkoWtllyeTN8dW3aM4P6YgKcmmPikKL5FdoyQhti4eh6RK5a5VrUXJTOlTNIpIHsfB5Iw==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@expo/package-manager": {
"version": "1.10.3",
"resolved": "https://registry.npmjs.org/@expo/package-manager/-/package-manager-1.10.3.tgz",
"integrity": "sha512-ZuXiK/9fCrIuLjPSe1VYmfp0Sa85kCMwd8QQpgyi5ufppYKRtLBg14QOgUqj8ZMbJTxE0xqzd0XR7kOs3vAK9A==",
"license": "MIT",
"dependencies": {
"@expo/json-file": "^10.0.12",
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"npm-package-arg": "^11.0.0",
"ora": "^3.4.0",
"resolve-workspace-root": "^2.0.0"
}
},
"node_modules/@expo/plist": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz",
"integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
}
},
"node_modules/@expo/require-utils": {
"version": "55.0.3",
"resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.3.tgz",
"integrity": "sha512-TS1m5tW45q4zoaTlt6DwmdYHxvFTIxoLrTHKOFrIirHIqIXnHCzpceg8wumiBi+ZXSaGY2gobTbfv+WVhJY6Fw==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"@babel/core": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8"
},
"peerDependencies": {
"typescript": "^5.0.0 || ^5.0.0-0"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/@expo/schema-utils": {
"version": "55.0.2",
"resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.2.tgz",
"integrity": "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g==",
"license": "MIT"
},
"node_modules/@expo/sdk-runtime-versions": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@expo/sdk-runtime-versions/-/sdk-runtime-versions-1.0.0.tgz",
"integrity": "sha512-Doz2bfiPndXYFPMRwPyGa1k5QaKDVpY806UJj570epIiMzWaYyCtobasyfC++qfIXVb5Ocy7r3tP9d62hAQ7IQ==",
"license": "MIT"
},
"node_modules/@expo/spawn-async": {
"version": "1.7.2",
"resolved": "https://registry.npmjs.org/@expo/spawn-async/-/spawn-async-1.7.2.tgz",
"integrity": "sha512-QdWi16+CHB9JYP7gma19OVVg0BFkvU8zNj9GjWorYI8Iv8FUxjOCcYRuAmX4s/h91e4e7BPsskc8cSrZYho9Ew==",
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@expo/sudo-prompt": {
"version": "9.3.2",
"resolved": "https://registry.npmjs.org/@expo/sudo-prompt/-/sudo-prompt-9.3.2.tgz",
"integrity": "sha512-HHQigo3rQWKMDzYDLkubN5WQOYXJJE2eNqIQC2axC2iO3mHdwnIR7FgZVvHWtBwAdzBgAP0ECp8KqS8TiMKvgw==",
"license": "MIT"
},
"node_modules/@expo/ws-tunnel": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/@expo/ws-tunnel/-/ws-tunnel-1.0.6.tgz",
"integrity": "sha512-nDRbLmSrJar7abvUjp3smDwH8HcbZcoOEa5jVPUv9/9CajgmWw20JNRwTuBRzWIWIkEJDkz20GoNA+tSwUqk0Q==",
"license": "MIT"
},
"node_modules/@expo/xcpretty": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/@expo/xcpretty/-/xcpretty-4.4.1.tgz",
"integrity": "sha512-KZNxZvnGCtiM2aYYZ6Wz0Ix5r47dAvpNLApFtZWnSoERzAdOMzVBOPysBoM0JlF6FKWZ8GPqgn6qt3dV/8Zlpg==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"chalk": "^4.1.0",
"js-yaml": "^4.1.0"
},
"bin": {
"excpretty": "build/cli.js"
}
},
"node_modules/@expo/xcpretty/node_modules/argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
"license": "Python-2.0"
},
"node_modules/@expo/xcpretty/node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/@ide/backoff": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@ide/backoff/-/backoff-1.0.0.tgz",
"integrity": "sha512-F0YfUDjvT+Mtt/R4xdl2X0EYCHMMiJqNLdxHD++jDT5ydEFIyqbCHh51Qx2E211dgZprPKhV7sHmnXKpLuvc5g==",
"license": "MIT"
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"license": "MIT"
},
"node_modules/@isaacs/cliui/node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@isaacs/cliui/node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/@isaacs/cliui/node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/@isaacs/ttlcache": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/@isaacs/ttlcache/-/ttlcache-1.4.1.tgz",
"integrity": "sha512-RQgQ4uQ+pLbqXfOmieB91ejmLwvSgv9nLx6sT6sD83s7umBypgg+OIBOBbEUiJXrfpnp9j0mRhYYdzp9uqq3lA==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
"integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==",
"license": "ISC",
"dependencies": {
"camelcase": "^5.3.1",
"find-up": "^4.1.0",
"get-package-type": "^0.1.0",
"js-yaml": "^3.13.1",
"resolve-from": "^5.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@istanbuljs/load-nyc-config/node_modules/camelcase": {
"version": "5.3.1",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
"integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/@istanbuljs/schema": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz",
"integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@jest/create-cache-key-function": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz",
"integrity": "sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/environment": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz",
"integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==",
"license": "MIT",
"dependencies": {
"@jest/fake-timers": "^29.7.0",
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-mock": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/fake-timers": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz",
"integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@sinonjs/fake-timers": "^10.0.2",
"@types/node": "*",
"jest-message-util": "^29.7.0",
"jest-mock": "^29.7.0",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/schemas": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz",
"integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==",
"license": "MIT",
"dependencies": {
"@sinclair/typebox": "^0.27.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/transform": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz",
"integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.11.6",
"@jest/types": "^29.6.3",
"@jridgewell/trace-mapping": "^0.3.18",
"babel-plugin-istanbul": "^6.1.1",
"chalk": "^4.0.0",
"convert-source-map": "^2.0.0",
"fast-json-stable-stringify": "^2.1.0",
"graceful-fs": "^4.2.9",
"jest-haste-map": "^29.7.0",
"jest-regex-util": "^29.6.3",
"jest-util": "^29.7.0",
"micromatch": "^4.0.4",
"pirates": "^4.0.4",
"slash": "^3.0.0",
"write-file-atomic": "^4.0.2"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jest/types": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz",
"integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==",
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"@types/istanbul-lib-coverage": "^2.0.0",
"@types/istanbul-reports": "^3.0.0",
"@types/node": "*",
"@types/yargs": "^17.0.8",
"chalk": "^4.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/remapping": {
"version": "2.3.5",
"resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
"integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.24"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
"license": "MIT",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/source-map": {
"version": "0.3.11",
"resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.11.tgz",
"integrity": "sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.5",
"@jridgewell/trace-mapping": "^0.3.25"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@react-native/assets-registry": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.2.tgz",
"integrity": "sha512-9I5l3pGAKnlpQ15uVkeB9Mgjvt3cZEaEc8EDtdexvdtZvLSjtwBzgourrOW4yZUijbjJr8h3YO2Y0q+THwUHTA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/babel-plugin-codegen": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.2.tgz",
"integrity": "sha512-XbcN/BEa64pVlb0Hb/E/Ph2SepjVN/FcNKrJcQvtaKZA6mBSO8pW8Eircdlr61/KBH94LihHbQoQDzkQFpeaTg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.25.3",
"@react-native/codegen": "0.83.2"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/babel-preset": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.2.tgz",
"integrity": "sha512-X/RAXDfe6W+om/Fw1i6htTxQXFhBJ2jgNOWx3WpI3KbjeIWbq7ib6vrpTeIAW2NUMg+K3mML1NzgD4dpZeqdjA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-dynamic-import": "^7.8.3",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-transform-arrow-functions": "^7.24.7",
"@babel/plugin-transform-async-generator-functions": "^7.25.4",
"@babel/plugin-transform-async-to-generator": "^7.24.7",
"@babel/plugin-transform-block-scoping": "^7.25.0",
"@babel/plugin-transform-class-properties": "^7.25.4",
"@babel/plugin-transform-classes": "^7.25.4",
"@babel/plugin-transform-computed-properties": "^7.24.7",
"@babel/plugin-transform-destructuring": "^7.24.8",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-for-of": "^7.24.7",
"@babel/plugin-transform-function-name": "^7.25.1",
"@babel/plugin-transform-literals": "^7.25.2",
"@babel/plugin-transform-logical-assignment-operators": "^7.24.7",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7",
"@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7",
"@babel/plugin-transform-numeric-separator": "^7.24.7",
"@babel/plugin-transform-object-rest-spread": "^7.24.7",
"@babel/plugin-transform-optional-catch-binding": "^7.24.7",
"@babel/plugin-transform-optional-chaining": "^7.24.8",
"@babel/plugin-transform-parameters": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-react-display-name": "^7.24.7",
"@babel/plugin-transform-react-jsx": "^7.25.2",
"@babel/plugin-transform-react-jsx-self": "^7.24.7",
"@babel/plugin-transform-react-jsx-source": "^7.24.7",
"@babel/plugin-transform-regenerator": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/plugin-transform-shorthand-properties": "^7.24.7",
"@babel/plugin-transform-spread": "^7.24.7",
"@babel/plugin-transform-sticky-regex": "^7.24.7",
"@babel/plugin-transform-typescript": "^7.25.2",
"@babel/plugin-transform-unicode-regex": "^7.24.7",
"@babel/template": "^7.25.0",
"@react-native/babel-plugin-codegen": "0.83.2",
"babel-plugin-syntax-hermes-parser": "0.32.0",
"babel-plugin-transform-flow-enums": "^0.0.2",
"react-refresh": "^0.14.0"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/codegen": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz",
"integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/parser": "^7.25.3",
"glob": "^7.1.1",
"hermes-parser": "0.32.0",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"yargs": "^17.6.2"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@babel/core": "*"
}
},
"node_modules/@react-native/codegen/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/@react-native/codegen/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/@react-native/codegen/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@react-native/codegen/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/@react-native/community-cli-plugin": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.2.tgz",
"integrity": "sha512-sTEF0eiUKtmImEP07Qo5c3Khvm1LIVX1Qyb6zWUqPL6W3MqFiXutZvKBjqLz6p49Szx8cplQLoXfLHT0bcDXKg==",
"license": "MIT",
"dependencies": {
"@react-native/dev-middleware": "0.83.2",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"metro": "^0.83.3",
"metro-config": "^0.83.3",
"metro-core": "^0.83.3",
"semver": "^7.1.3"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@react-native-community/cli": "*",
"@react-native/metro-config": "*"
},
"peerDependenciesMeta": {
"@react-native-community/cli": {
"optional": true
},
"@react-native/metro-config": {
"optional": true
}
}
},
"node_modules/@react-native/debugger-frontend": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.2.tgz",
"integrity": "sha512-t4fYfa7xopbUF5S4+ihNEwgaq4wLZLKLY0Ms8z72lkMteVd3bOX2Foxa8E2wTfRvdhPOkSpOsTeNDmD8ON4DoQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/debugger-shell": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.2.tgz",
"integrity": "sha512-z9go6NJMsLSDJT5MW6VGugRsZHjYvUTwxtsVc3uLt4U9W6T3J6FWI2wHpXIzd2dUkXRfAiRQ3Zi8ZQQ8fRFg9A==",
"license": "MIT",
"dependencies": {
"cross-spawn": "^7.0.6",
"fb-dotslash": "0.5.8"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/dev-middleware": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.2.tgz",
"integrity": "sha512-Zi4EVaAm28+icD19NN07Gh8Pqg/84QQu+jn4patfWKNkcToRFP5vPEbbp0eLOGWS+BVB1d1Fn5lvMrJsBbFcOg==",
"license": "MIT",
"dependencies": {
"@isaacs/ttlcache": "^1.4.1",
"@react-native/debugger-frontend": "0.83.2",
"@react-native/debugger-shell": "0.83.2",
"chrome-launcher": "^0.15.2",
"chromium-edge-launcher": "^0.2.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"invariant": "^2.2.4",
"nullthrows": "^1.1.1",
"open": "^7.0.3",
"serve-static": "^1.16.2",
"ws": "^7.5.10"
},
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/gradle-plugin": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.2.tgz",
"integrity": "sha512-PqN11fXRAU+uJ0inZY1HWYlwJOXHOhF4SPyeHBBxjajKpm2PGunmvFWwkmBjmmUkP/CNO0ezTUudV0oj+2wiHQ==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/js-polyfills": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.2.tgz",
"integrity": "sha512-dk6fIY2OrKW/2Nk2HydfYNrQau8g6LOtd7NVBrgaqa+lvuRyIML5iimShP5qPqQnx2ofHuzjFw+Ya0b5Q7nDbA==",
"license": "MIT",
"engines": {
"node": ">= 20.19.4"
}
},
"node_modules/@react-native/normalize-colors": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.2.tgz",
"integrity": "sha512-gkZAb9LoVVzNuYzzOviH7DiPTXQoZPHuiTH2+O2+VWNtOkiznjgvqpwYAhg58a5zfRq5GXlbBdf5mzRj5+3Y5Q==",
"license": "MIT"
},
"node_modules/@react-navigation/bottom-tabs": {
"version": "7.15.7",
"resolved": "https://registry.npmjs.org/@react-navigation/bottom-tabs/-/bottom-tabs-7.15.7.tgz",
"integrity": "sha512-OXNvU0esvyZf//KpaIsFh2GD1otxqG+Lv48VfkNIh+3DEbOnqni8pOrKdICgoC4T0R8oVln7pnVeHl89Gipv8w==",
"license": "MIT",
"dependencies": {
"@react-navigation/elements": "^2.9.12",
"color": "^4.2.3",
"sf-symbols-typescript": "^2.1.0"
},
"peerDependencies": {
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
"react-native-screens": ">= 4.0.0"
}
},
"node_modules/@react-navigation/core": {
"version": "7.17.0",
"resolved": "https://registry.npmjs.org/@react-navigation/core/-/core-7.17.0.tgz",
"integrity": "sha512-E4Kr1PRrhKiVn1RdMdPIG1rCfrKh+HiVJ2smdLsh9D95Q2z0a9dGE9yHpRQ2pAUiiwOfgloLqegkPb8g+TcCBA==",
"license": "MIT",
"dependencies": {
"@react-navigation/routers": "^7.5.3",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"query-string": "^7.1.3",
"react-is": "^19.1.0",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"react": ">= 18.2.0"
}
},
"node_modules/@react-navigation/core/node_modules/react-is": {
"version": "19.2.4",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-19.2.4.tgz",
"integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==",
"license": "MIT"
},
"node_modules/@react-navigation/elements": {
"version": "2.9.12",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-2.9.12.tgz",
"integrity": "sha512-LSaQUj5SV9OXVRcxT8mqETDoM7BOKCveCvuLjdAr9NZnPDM5HW8uDnvW/sCa8oEFy+22+ojoXtHFKsfnesgBbw==",
"license": "MIT",
"dependencies": {
"color": "^4.2.3",
"use-latest-callback": "^0.2.4",
"use-sync-external-store": "^1.5.0"
},
"peerDependencies": {
"@react-native-masked-view/masked-view": ">= 0.2.0",
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0"
},
"peerDependenciesMeta": {
"@react-native-masked-view/masked-view": {
"optional": true
}
}
},
"node_modules/@react-navigation/native": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@react-navigation/native/-/native-7.2.0.tgz",
"integrity": "sha512-kEuqIS1MkzzLD45Fp17CrxAchoB4W6tMfc541merUgtAeNNsg06gRrvmuLv6LAYvpLifGdXuSjpluPIu/VmbQw==",
"license": "MIT",
"dependencies": {
"@react-navigation/core": "^7.17.0",
"escape-string-regexp": "^4.0.0",
"fast-deep-equal": "^3.1.3",
"nanoid": "^3.3.11",
"use-latest-callback": "^0.2.4"
},
"peerDependencies": {
"react": ">= 18.2.0",
"react-native": "*"
}
},
"node_modules/@react-navigation/native-stack": {
"version": "7.14.7",
"resolved": "https://registry.npmjs.org/@react-navigation/native-stack/-/native-stack-7.14.7.tgz",
"integrity": "sha512-MxKdKS817YK7iirlyW+XZnXJ339eRE7aA3E55zHVDS+R+bqro+PwRwNGqL1Y9e3w0KjAKZVsOfn5erJRWrO4iQ==",
"license": "MIT",
"dependencies": {
"@react-navigation/elements": "^2.9.12",
"color": "^4.2.3",
"sf-symbols-typescript": "^2.1.0",
"warn-once": "^0.1.1"
},
"peerDependencies": {
"@react-navigation/native": "^7.2.0",
"react": ">= 18.2.0",
"react-native": "*",
"react-native-safe-area-context": ">= 4.0.0",
"react-native-screens": ">= 4.0.0"
}
},
"node_modules/@react-navigation/routers": {
"version": "7.5.3",
"resolved": "https://registry.npmjs.org/@react-navigation/routers/-/routers-7.5.3.tgz",
"integrity": "sha512-1tJHg4KKRJuQ1/EvJxatrMef3NZXEPzwUIUZ3n1yJ2t7Q97siwRtbynRpQG9/69ebbtiZ8W3ScOZF/OmhvM4Rg==",
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.11"
}
},
"node_modules/@sinclair/typebox": {
"version": "0.27.10",
"resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz",
"integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==",
"license": "MIT"
},
"node_modules/@sinonjs/commons": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz",
"integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==",
"license": "BSD-3-Clause",
"dependencies": {
"type-detect": "4.0.8"
}
},
"node_modules/@sinonjs/fake-timers": {
"version": "10.3.0",
"resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz",
"integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==",
"license": "BSD-3-Clause",
"dependencies": {
"@sinonjs/commons": "^3.0.0"
}
},
"node_modules/@supabase/auth-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.100.0.tgz",
"integrity": "sha512-pdT3ye3UVRN1Cg0wom6BmyY+XTtp5DiJaYnPi6j8ht5i8Lq8kfqxJMJz9GI9YDKk3w1nhGOPnh6Qz5qpyYm+1w==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/functions-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.100.0.tgz",
"integrity": "sha512-keLg79RPwP+uiwHuxFPTFgDRxPV46LM4j/swjyR2GKJgWniTVSsgiBHfbIBDcrQwehLepy09b/9QSHUywtKRWQ==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/phoenix": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@supabase/phoenix/-/phoenix-0.4.0.tgz",
"integrity": "sha512-RHSx8bHS02xwfHdAbX5Lpbo6PXbgyf7lTaXTlwtFDPwOIw64NnVRwFAXGojHhjtVYI+PEPNSWwkL90f4agN3bw==",
"license": "MIT"
},
"node_modules/@supabase/postgrest-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.100.0.tgz",
"integrity": "sha512-xYNvNbBJaXOGcrZ44wxwp5830uo1okMHGS8h8dm3u4f0xcZ39yzbryUsubTJW41MG2gbL/6U57cA4Pi6YMZ9pA==",
"license": "MIT",
"dependencies": {
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.100.0.tgz",
"integrity": "sha512-2AZs00zzEF0HuCKY8grz5eCYlwEfVi5HONLZFoNR6aDfxQivl8zdQYNjyFoqN2MZiVhQHD7u6XV/xHwM8mCEHw==",
"license": "MIT",
"dependencies": {
"@supabase/phoenix": "^0.4.0",
"@types/ws": "^8.18.1",
"tslib": "2.8.1",
"ws": "^8.18.2"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/realtime-js/node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/@supabase/storage-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.100.0.tgz",
"integrity": "sha512-d4EeuK6RNIgYNA2MU9kj8lQrLm5AzZ+WwpWjGkii6SADQNIGTC/uiaTRu02XJ5AmFALQfo8fLl9xuCkO6Xw+iQ==",
"license": "MIT",
"dependencies": {
"iceberg-js": "^0.8.1",
"tslib": "2.8.1"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@supabase/supabase-js": {
"version": "2.100.0",
"resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.100.0.tgz",
"integrity": "sha512-r0tlcukejJXJ1m/2eG/Ya5eYs4W8AC7oZfShpG3+SIo/eIU9uIt76ZeYI1SoUwUmcmzlAbgch+HDZDR/toVQPQ==",
"license": "MIT",
"dependencies": {
"@supabase/auth-js": "2.100.0",
"@supabase/functions-js": "2.100.0",
"@supabase/postgrest-js": "2.100.0",
"@supabase/realtime-js": "2.100.0",
"@supabase/storage-js": "2.100.0"
},
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/@tanstack/query-core": {
"version": "5.95.2",
"resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.95.2.tgz",
"integrity": "sha512-o4T8vZHZET4Bib3jZ/tCW9/7080urD4c+0/AUaYVpIqOsr7y0reBc1oX3ttNaSW5mYyvZHctiQ/UOP2PfdmFEQ==",
"license": "MIT",
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@tanstack/react-query": {
"version": "5.95.2",
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.95.2.tgz",
"integrity": "sha512-/wGkvLj/st5Ud1Q76KF1uFxScV7WeqN1slQx5280ycwAyYkIPGaRZAEgHxe3bjirSd5Zpwkj6zNcR4cqYni/ZA==",
"license": "MIT",
"dependencies": {
"@tanstack/query-core": "5.95.2"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/tannerlinsley"
},
"peerDependencies": {
"react": "^18 || ^19"
}
},
"node_modules/@types/babel__core": {
"version": "7.20.5",
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
"integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.20.7",
"@babel/types": "^7.20.7",
"@types/babel__generator": "*",
"@types/babel__template": "*",
"@types/babel__traverse": "*"
}
},
"node_modules/@types/babel__generator": {
"version": "7.27.0",
"resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
"integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__template": {
"version": "7.4.4",
"resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
"integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
"license": "MIT",
"dependencies": {
"@babel/parser": "^7.1.0",
"@babel/types": "^7.0.0"
}
},
"node_modules/@types/babel__traverse": {
"version": "7.28.0",
"resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
"integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.28.2"
}
},
"node_modules/@types/emscripten": {
"version": "1.41.5",
"resolved": "https://registry.npmjs.org/@types/emscripten/-/emscripten-1.41.5.tgz",
"integrity": "sha512-cMQm7pxu6BxtHyqJ7mQZ2kXWV5SLmugybFdHCBbJ5eHzOo6VhBckEgAT3//rP5FwPHNPeEiq4SmQ5ucBwsOo4Q==",
"license": "MIT"
},
"node_modules/@types/geojson": {
"version": "7946.0.16",
"resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz",
"integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==",
"license": "MIT"
},
"node_modules/@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
"integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/hammerjs": {
"version": "2.0.46",
"resolved": "https://registry.npmjs.org/@types/hammerjs/-/hammerjs-2.0.46.tgz",
"integrity": "sha512-ynRvcq6wvqexJ9brDMS4BnBLzmr0e14d6ZJTEShTBWKymQiHwlAyGu0ZPEFI2Fh1U53F7tN9ufClWM5KvqkKOw==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-coverage": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
"integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
"license": "MIT"
},
"node_modules/@types/istanbul-lib-report": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
"integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-coverage": "*"
}
},
"node_modules/@types/istanbul-reports": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
"integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
"license": "MIT",
"dependencies": {
"@types/istanbul-lib-report": "*"
}
},
"node_modules/@types/node": {
"version": "25.5.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.5.0.tgz",
"integrity": "sha512-jp2P3tQMSxWugkCUKLRPVUpGaL5MVFwF8RDuSRztfwgN1wmqJeMSbKlnEtQqU8UrhTmzEmZdu2I6v2dpp7XIxw==",
"license": "MIT",
"dependencies": {
"undici-types": "~7.18.0"
}
},
"node_modules/@types/react": {
"version": "19.2.14",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
"integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.2.2"
}
},
"node_modules/@types/stack-utils": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
"license": "MIT"
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
"integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==",
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/yargs": {
"version": "17.0.35",
"resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz",
"integrity": "sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==",
"license": "MIT",
"dependencies": {
"@types/yargs-parser": "*"
}
},
"node_modules/@types/yargs-parser": {
"version": "21.0.3",
"resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
"integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
"license": "MIT"
},
"node_modules/@ungap/structured-clone": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz",
"integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==",
"license": "ISC"
},
"node_modules/@xmldom/xmldom": {
"version": "0.8.11",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.11.tgz",
"integrity": "sha512-cQzWCtO6C8TQiYl1ruKNn2U6Ao4o4WBBcbL61yJl84x+j5sOWWFU9X7DpND8XZG3daDppSsigMdfAIl2upQBRw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"license": "MIT",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/accepts": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
"integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==",
"license": "MIT",
"dependencies": {
"mime-types": "~2.1.34",
"negotiator": "0.6.3"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/acorn": {
"version": "8.16.0",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
"integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
"license": "MIT",
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/anser": {
"version": "1.4.10",
"resolved": "https://registry.npmjs.org/anser/-/anser-1.4.10.tgz",
"integrity": "sha512-hCv9AqTQ8ycjpSd3upOJd7vFwW1JaoYQ7tpham03GJ1ca8/65rqn0RpaWpItOAd6ylW9wAw6luXYPJIyPFVOww==",
"license": "MIT"
},
"node_modules/ansi-escapes": {
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.21.3"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-escapes/node_modules/type-fest": {
"version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/any-promise": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
"integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
"license": "MIT"
},
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/arg": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
"integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
"license": "MIT"
},
"node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==",
"license": "MIT"
},
"node_modules/assert": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz",
"integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.2",
"is-nan": "^1.3.2",
"object-is": "^1.1.5",
"object.assign": "^4.1.4",
"util": "^0.12.5"
}
},
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
"integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
"license": "MIT",
"dependencies": {
"possible-typed-array-names": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/babel-jest": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz",
"integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==",
"license": "MIT",
"dependencies": {
"@jest/transform": "^29.7.0",
"@types/babel__core": "^7.1.14",
"babel-plugin-istanbul": "^6.1.1",
"babel-preset-jest": "^29.6.3",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"slash": "^3.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"@babel/core": "^7.8.0"
}
},
"node_modules/babel-plugin-istanbul": {
"version": "6.1.1",
"resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz",
"integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/helper-plugin-utils": "^7.0.0",
"@istanbuljs/load-nyc-config": "^1.0.0",
"@istanbuljs/schema": "^0.1.2",
"istanbul-lib-instrument": "^5.0.4",
"test-exclude": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/babel-plugin-jest-hoist": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz",
"integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==",
"license": "MIT",
"dependencies": {
"@babel/template": "^7.3.3",
"@babel/types": "^7.3.3",
"@types/babel__core": "^7.1.14",
"@types/babel__traverse": "^7.0.6"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2": {
"version": "0.4.17",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz",
"integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==",
"license": "MIT",
"dependencies": {
"@babel/compat-data": "^7.28.6",
"@babel/helper-define-polyfill-provider": "^0.6.8",
"semver": "^6.3.1"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-polyfill-corejs2/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/babel-plugin-polyfill-corejs3": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.13.0.tgz",
"integrity": "sha512-U+GNwMdSFgzVmfhNm8GJUX88AadB3uo9KpJqS3FaqNIPKgySuvMb+bHPsOmmuWyIcuqZj/pzt1RUIUZns4y2+A==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.5",
"core-js-compat": "^3.43.0"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-polyfill-regenerator": {
"version": "0.6.8",
"resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz",
"integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==",
"license": "MIT",
"dependencies": {
"@babel/helper-define-polyfill-provider": "^0.6.8"
},
"peerDependencies": {
"@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0"
}
},
"node_modules/babel-plugin-react-compiler": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz",
"integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==",
"license": "MIT",
"dependencies": {
"@babel/types": "^7.26.0"
}
},
"node_modules/babel-plugin-react-native-web": {
"version": "0.21.2",
"resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz",
"integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==",
"license": "MIT"
},
"node_modules/babel-plugin-syntax-hermes-parser": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz",
"integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==",
"license": "MIT",
"dependencies": {
"hermes-parser": "0.32.0"
}
},
"node_modules/babel-plugin-transform-flow-enums": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/babel-plugin-transform-flow-enums/-/babel-plugin-transform-flow-enums-0.0.2.tgz",
"integrity": "sha512-g4aaCrDDOsWjbm0PUUeVnkcVd6AKJsVc/MbnPhEotEpkeJQP6b8nzewohQi7+QS8UyPehOhGWn0nOwjvWpmMvQ==",
"license": "MIT",
"dependencies": {
"@babel/plugin-syntax-flow": "^7.12.1"
}
},
"node_modules/babel-preset-current-node-syntax": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.2.0.tgz",
"integrity": "sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==",
"license": "MIT",
"dependencies": {
"@babel/plugin-syntax-async-generators": "^7.8.4",
"@babel/plugin-syntax-bigint": "^7.8.3",
"@babel/plugin-syntax-class-properties": "^7.12.13",
"@babel/plugin-syntax-class-static-block": "^7.14.5",
"@babel/plugin-syntax-import-attributes": "^7.24.7",
"@babel/plugin-syntax-import-meta": "^7.10.4",
"@babel/plugin-syntax-json-strings": "^7.8.3",
"@babel/plugin-syntax-logical-assignment-operators": "^7.10.4",
"@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3",
"@babel/plugin-syntax-numeric-separator": "^7.10.4",
"@babel/plugin-syntax-object-rest-spread": "^7.8.3",
"@babel/plugin-syntax-optional-catch-binding": "^7.8.3",
"@babel/plugin-syntax-optional-chaining": "^7.8.3",
"@babel/plugin-syntax-private-property-in-object": "^7.14.5",
"@babel/plugin-syntax-top-level-await": "^7.14.5"
},
"peerDependencies": {
"@babel/core": "^7.0.0 || ^8.0.0-0"
}
},
"node_modules/babel-preset-jest": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz",
"integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==",
"license": "MIT",
"dependencies": {
"babel-plugin-jest-hoist": "^29.6.3",
"babel-preset-current-node-syntax": "^1.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"peerDependencies": {
"@babel/core": "^7.0.0"
}
},
"node_modules/badgin": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/badgin/-/badgin-1.2.3.tgz",
"integrity": "sha512-NQGA7LcfCpSzIbGRbkgjgdWkjy7HI+Th5VLxTJfW5EeaAf3fnS+xWQaQOCYiny+q6QSvxqoSO04vCx+4u++EJw==",
"license": "MIT"
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
"integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
"license": "MIT",
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/barcode-detector": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/barcode-detector/-/barcode-detector-3.1.1.tgz",
"integrity": "sha512-ghWlEAV93ZCUniO7Co3ih/01XPm+U30CV+NoPbO6Chj5lZzHydDAqKlrBEd+37TkoR+QTH3tnnwd8k8epGTfIg==",
"license": "MIT",
"dependencies": {
"zxing-wasm": "3.0.1"
}
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.10.10",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
"integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
"license": "Apache-2.0",
"bin": {
"baseline-browser-mapping": "dist/cli.cjs"
},
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/better-opn": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/better-opn/-/better-opn-3.0.2.tgz",
"integrity": "sha512-aVNobHnJqLiUelTaHat9DZ1qM2w0C0Eym4LPI/3JxOnSokGVdsl1T1kN7TFvsEAD8G47A6VKQ0TVHqbBnYMJlQ==",
"license": "MIT",
"dependencies": {
"open": "^8.0.4"
},
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/better-opn/node_modules/open": {
"version": "8.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-8.4.2.tgz",
"integrity": "sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==",
"license": "MIT",
"dependencies": {
"define-lazy-prop": "^2.0.0",
"is-docker": "^2.1.1",
"is-wsl": "^2.2.0"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/big-integer": {
"version": "1.6.52",
"resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz",
"integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==",
"license": "Unlicense",
"engines": {
"node": ">=0.6"
}
},
"node_modules/bplist-creator": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/bplist-creator/-/bplist-creator-0.1.0.tgz",
"integrity": "sha512-sXaHZicyEEmY86WyueLTQesbeoH/mquvarJaQNbjuOQO+7gbFcDEWqKmcWA4cOTLzFlfgvkiVxolk1k5bBIpmg==",
"license": "MIT",
"dependencies": {
"stream-buffers": "2.2.x"
}
},
"node_modules/bplist-parser": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
"integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
"license": "MIT",
"dependencies": {
"big-integer": "1.6.x"
},
"engines": {
"node": ">= 5.10.0"
}
},
"node_modules/brace-expansion": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
"integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/browserslist": {
"version": "4.28.1",
"resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
"integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
"electron-to-chromium": "^1.5.263",
"node-releases": "^2.0.27",
"update-browserslist-db": "^1.2.0"
},
"bin": {
"browserslist": "cli.js"
},
"engines": {
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
}
},
"node_modules/bser": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz",
"integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==",
"license": "Apache-2.0",
"dependencies": {
"node-int64": "^0.4.0"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==",
"license": "MIT"
},
"node_modules/bytes": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
"integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/call-bind": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz",
"integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"get-intrinsic": "^1.3.0",
"set-function-length": "^1.2.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/call-bind-apply-helpers": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/call-bound": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
"integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"get-intrinsic": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/camelcase": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
"integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001781",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001781.tgz",
"integrity": "sha512-RdwNCyMsNBftLjW6w01z8bKEvT6e/5tpPVEgtn22TiLGlstHOVecsX2KHFkD5e/vRnIE4EGzpuIODb3mtswtkw==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "CC-BY-4.0"
},
"node_modules/chalk": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
"node_modules/chrome-launcher": {
"version": "0.15.2",
"resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz",
"integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"escape-string-regexp": "^4.0.0",
"is-wsl": "^2.2.0",
"lighthouse-logger": "^1.0.0"
},
"bin": {
"print-chrome-path": "bin/print-chrome-path.js"
},
"engines": {
"node": ">=12.13.0"
}
},
"node_modules/chromium-edge-launcher": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/chromium-edge-launcher/-/chromium-edge-launcher-0.2.0.tgz",
"integrity": "sha512-JfJjUnq25y9yg4FABRRVPmBGWPZZi+AQXT4mxupb67766/0UlhG8PAZCz6xzEMXTbW3CsSoE8PcCWA49n35mKg==",
"license": "Apache-2.0",
"dependencies": {
"@types/node": "*",
"escape-string-regexp": "^4.0.0",
"is-wsl": "^2.2.0",
"lighthouse-logger": "^1.0.0",
"mkdirp": "^1.0.4",
"rimraf": "^3.0.2"
}
},
"node_modules/ci-info": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz",
"integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==",
"license": "MIT"
},
"node_modules/cli-cursor": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
"integrity": "sha512-8lgKz8LmCRYZZQDpRyT2m5rKJ08TnU4tR9FFFW2rxpxR1FzWi4PQ/NfyODchAatHaUgnSPVcx/R5w6NuTBzFiw==",
"license": "MIT",
"dependencies": {
"restore-cursor": "^2.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/cli-spinners": {
"version": "2.9.2",
"resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz",
"integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==",
"license": "MIT",
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"license": "MIT",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
"integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1",
"color-string": "^1.9.0"
},
"engines": {
"node": ">=12.5.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/color-string": {
"version": "1.9.1",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
"integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
"license": "MIT",
"dependencies": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"node_modules/commander": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
"license": "MIT",
"engines": {
"node": ">= 10"
}
},
"node_modules/compressible": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz",
"integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==",
"license": "MIT",
"dependencies": {
"mime-db": ">= 1.43.0 < 2"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/compression": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz",
"integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==",
"license": "MIT",
"dependencies": {
"bytes": "3.1.2",
"compressible": "~2.0.18",
"debug": "2.6.9",
"negotiator": "~0.6.4",
"on-headers": "~1.1.0",
"safe-buffer": "5.2.1",
"vary": "~1.1.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/compression/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/compression/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/compression/node_modules/negotiator": {
"version": "0.6.4",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz",
"integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"license": "MIT"
},
"node_modules/connect": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz",
"integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"finalhandler": "1.1.2",
"parseurl": "~1.3.3",
"utils-merge": "1.0.1"
},
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/connect/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/connect/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/convert-source-map": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
"integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
"license": "MIT"
},
"node_modules/core-js-compat": {
"version": "3.49.0",
"resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz",
"integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==",
"license": "MIT",
"dependencies": {
"browserslist": "^4.28.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"license": "MIT",
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/crypto-random-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz",
"integrity": "sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/csstype": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
"license": "MIT",
"engines": {
"node": ">=0.10"
}
},
"node_modules/deepmerge": {
"version": "4.3.1",
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz",
"integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
"integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==",
"license": "MIT",
"dependencies": {
"clone": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
"integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0",
"es-errors": "^1.3.0",
"gopd": "^1.0.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/define-lazy-prop": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz",
"integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/define-properties": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
"integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.0.1",
"has-property-descriptors": "^1.0.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/destroy": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz",
"integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==",
"license": "MIT",
"engines": {
"node": ">= 0.8",
"npm": "1.2.8000 || >= 1.4.16"
}
},
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=8"
}
},
"node_modules/dnssd-advertise": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.3.tgz",
"integrity": "sha512-XENsHi3MBzWOCAXif3yZvU1Ah0l+nhJj1sjWL6TnOAYKvGiFhbTx32xHN7+wLMLUOCj7Nr0evADWG4R8JtqCDA==",
"license": "MIT"
},
"node_modules/dotenv": {
"version": "16.4.7",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz",
"integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dotenv-expand": {
"version": "11.0.7",
"resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-11.0.7.tgz",
"integrity": "sha512-zIHwmZPRshsCdpMDyVsqGmgyP0yT8GAgXUnkdAoJisxvf33k7yO6OuoKmcTGuXPWSsm8Oh88nZicRLA9Y0rUeA==",
"license": "BSD-2-Clause",
"dependencies": {
"dotenv": "^16.4.5"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://dotenvx.com"
}
},
"node_modules/dunder-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
"es-errors": "^1.3.0",
"gopd": "^1.2.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"license": "MIT"
},
"node_modules/ee-first": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.321",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
"integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/error-stack-parser": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/error-stack-parser/-/error-stack-parser-2.1.4.tgz",
"integrity": "sha512-Sk5V6wVazPhq5MhpO+AUxJn5x7XSXGl1R93Vn7i+zS15KDVxQijejNCrz8340/2bgLBjR9GtEG8ZVKONDjcqGQ==",
"license": "MIT",
"dependencies": {
"stackframe": "^1.3.4"
}
},
"node_modules/es-define-property": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-errors": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/es-object-atoms": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
"integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
"integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==",
"license": "MIT"
},
"node_modules/escape-string-regexp": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
"license": "BSD-2-Clause",
"bin": {
"esparse": "bin/esparse.js",
"esvalidate": "bin/esvalidate.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/etag": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/expo": {
"version": "55.0.8",
"resolved": "https://registry.npmjs.org/expo/-/expo-55.0.8.tgz",
"integrity": "sha512-sziDGiDmeRmaSpFwMuSxFhr4vfWrQS1UgVXSTovsUDY0ximABzYdnF5L2OwtD8zjtIww8x2oJGmD6mKS+AoVsw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.20.0",
"@expo/cli": "55.0.18",
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/devtools": "55.0.2",
"@expo/fingerprint": "0.16.6",
"@expo/local-build-cache-provider": "55.0.7",
"@expo/log-box": "55.0.7",
"@expo/metro": "~54.2.0",
"@expo/metro-config": "55.0.11",
"@expo/vector-icons": "^15.0.2",
"@ungap/structured-clone": "^1.3.0",
"babel-preset-expo": "~55.0.12",
"expo-asset": "~55.0.10",
"expo-constants": "~55.0.9",
"expo-file-system": "~55.0.11",
"expo-font": "~55.0.4",
"expo-keep-awake": "~55.0.4",
"expo-modules-autolinking": "55.0.11",
"expo-modules-core": "55.0.17",
"pretty-format": "^29.7.0",
"react-refresh": "^0.14.2",
"whatwg-url-minimum": "^0.1.1"
},
"bin": {
"expo": "bin/cli",
"expo-modules-autolinking": "bin/autolinking",
"fingerprint": "bin/fingerprint"
},
"peerDependencies": {
"@expo/dom-webview": "*",
"@expo/metro-runtime": "*",
"react": "*",
"react-native": "*",
"react-native-webview": "*"
},
"peerDependenciesMeta": {
"@expo/dom-webview": {
"optional": true
},
"@expo/metro-runtime": {
"optional": true
},
"react-native-webview": {
"optional": true
}
}
},
"node_modules/expo-application": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/expo-application/-/expo-application-6.1.5.tgz",
"integrity": "sha512-ToImFmzw8luY043pWFJhh2ZMm4IwxXoHXxNoGdlhD4Ym6+CCmkAvCglg0FK8dMLzAb+/XabmOE7Rbm8KZb6NZg==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-camera": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/expo-camera/-/expo-camera-55.0.10.tgz",
"integrity": "sha512-ftDNJbGsAPNJ/QrM3j6g8/rQAOqTwZpqtvmzF7V9VX0movaCznZFdYsLi/Fff9WeEk1KzcnLIlmSz4Tj+BCrJA==",
"license": "MIT",
"dependencies": {
"barcode-detector": "^3.0.0"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*",
"react-native-web": "*"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/expo-constants": {
"version": "17.1.8",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-17.1.8.tgz",
"integrity": "sha512-sOCeMN/BWLA7hBP6lMwoEQzFNgTopk6YY03sBAmwT216IHyL54TjNseg8CRU1IQQ/+qinJ2fYWCl7blx2TiNcA==",
"license": "MIT",
"dependencies": {
"@expo/config": "~11.0.13",
"@expo/env": "~1.0.7"
},
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo-constants/node_modules/@babel/code-frame": {
"version": "7.10.4",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz",
"integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==",
"license": "MIT",
"dependencies": {
"@babel/highlight": "^7.10.4"
}
},
"node_modules/expo-constants/node_modules/@expo/config": {
"version": "11.0.13",
"resolved": "https://registry.npmjs.org/@expo/config/-/config-11.0.13.tgz",
"integrity": "sha512-TnGb4u/zUZetpav9sx/3fWK71oCPaOjZHoVED9NaEncktAd0Eonhq5NUghiJmkUGt3gGSjRAEBXiBbbY9/B1LA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "~7.10.4",
"@expo/config-plugins": "~10.1.2",
"@expo/config-types": "^53.0.5",
"@expo/json-file": "^9.1.5",
"deepmerge": "^4.3.1",
"getenv": "^2.0.0",
"glob": "^10.4.2",
"require-from-string": "^2.0.2",
"resolve-from": "^5.0.0",
"resolve-workspace-root": "^2.0.0",
"semver": "^7.6.0",
"slugify": "^1.3.4",
"sucrase": "3.35.0"
}
},
"node_modules/expo-constants/node_modules/@expo/config-plugins": {
"version": "10.1.2",
"resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-10.1.2.tgz",
"integrity": "sha512-IMYCxBOcnuFStuK0Ay+FzEIBKrwW8OVUMc65+v0+i7YFIIe8aL342l7T4F8lR4oCfhXn7d6M5QPgXvjtc/gAcw==",
"license": "MIT",
"dependencies": {
"@expo/config-types": "^53.0.5",
"@expo/json-file": "~9.1.5",
"@expo/plist": "^0.3.5",
"@expo/sdk-runtime-versions": "^1.0.0",
"chalk": "^4.1.2",
"debug": "^4.3.5",
"getenv": "^2.0.0",
"glob": "^10.4.2",
"resolve-from": "^5.0.0",
"semver": "^7.5.4",
"slash": "^3.0.0",
"slugify": "^1.6.6",
"xcode": "^3.0.1",
"xml2js": "0.6.0"
}
},
"node_modules/expo-constants/node_modules/@expo/config-types": {
"version": "53.0.5",
"resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-53.0.5.tgz",
"integrity": "sha512-kqZ0w44E+HEGBjy+Lpyn0BVL5UANg/tmNixxaRMLS6nf37YsDrLk2VMAmeKMMk5CKG0NmOdVv3ngeUjRQMsy9g==",
"license": "MIT"
},
"node_modules/expo-constants/node_modules/@expo/env": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@expo/env/-/env-1.0.7.tgz",
"integrity": "sha512-qSTEnwvuYJ3umapO9XJtrb1fAqiPlmUUg78N0IZXXGwQRt+bkp0OBls+Y5Mxw/Owj8waAM0Z3huKKskRADR5ow==",
"license": "MIT",
"dependencies": {
"chalk": "^4.0.0",
"debug": "^4.3.4",
"dotenv": "~16.4.5",
"dotenv-expand": "~11.0.6",
"getenv": "^2.0.0"
}
},
"node_modules/expo-constants/node_modules/@expo/json-file": {
"version": "9.1.5",
"resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-9.1.5.tgz",
"integrity": "sha512-prWBhLUlmcQtvN6Y7BpW2k9zXGd3ySa3R6rAguMJkp1z22nunLN64KYTUWfijFlprFoxm9r2VNnGkcbndAlgKA==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "~7.10.4",
"json5": "^2.2.3"
}
},
"node_modules/expo-constants/node_modules/@expo/plist": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.3.5.tgz",
"integrity": "sha512-9RYVU1iGyCJ7vWfg3e7c/NVyMFs8wbl+dMWZphtFtsqyN9zppGREU3ctlD3i8KUE0sCUTVnLjCWr+VeUIDep2g==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.2.3",
"xmlbuilder": "^15.1.1"
}
},
"node_modules/expo-constants/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/expo-constants/node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/expo-constants/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-constants/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/expo-constants/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-constants/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/expo-image-loader": {
"version": "55.0.0",
"resolved": "https://registry.npmjs.org/expo-image-loader/-/expo-image-loader-55.0.0.tgz",
"integrity": "sha512-NOjp56wDrfuA5aiNAybBIjqIn1IxKeGJ8CECWZncQ/GzjZfyTYAHTCyeApYkdKkMBLHINzI4BbTGSlbCa0fXXQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-image-picker": {
"version": "55.0.13",
"resolved": "https://registry.npmjs.org/expo-image-picker/-/expo-image-picker-55.0.13.tgz",
"integrity": "sha512-G+W11rcoUi3rK+6cnKWkTfZilMkGVZnYe90TiM3R98nPSlzGBoto3a/TkGGTJXedz/dmMzr49L+STlWhuKKIFw==",
"license": "MIT",
"dependencies": {
"expo-image-loader": "~55.0.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-linking": {
"version": "7.1.7",
"resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-7.1.7.tgz",
"integrity": "sha512-ZJaH1RIch2G/M3hx2QJdlrKbYFUTOjVVW4g39hfxrE5bPX9xhZUYXqxqQtzMNl1ylAevw9JkgEfWbBWddbZ3UA==",
"license": "MIT",
"dependencies": {
"expo-constants": "~17.1.7",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-location": {
"version": "55.1.4",
"resolved": "https://registry.npmjs.org/expo-location/-/expo-location-55.1.4.tgz",
"integrity": "sha512-0QWQ4QP8I6svtGUL895Y8bvKM3nGUWNp/MdCoCQNH3uAuwvLf2TQ6ZDVD3kdlkl2wGGdw6ziHQIYDHL96OlfnA==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.8.12"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-modules-autolinking": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.11.tgz",
"integrity": "sha512-9dqnPzQoIl1dIvEctMWpQ8eaiXDeBTgAwebCc1WF0BbEo+pcdKjZWoCSqlLj+d7IX+OnTgM+k6cY2kPDGIu4sg==",
"license": "MIT",
"dependencies": {
"@expo/require-utils": "^55.0.3",
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.1.0",
"commander": "^7.2.0"
},
"bin": {
"expo-modules-autolinking": "bin/expo-modules-autolinking.js"
}
},
"node_modules/expo-modules-core": {
"version": "55.0.17",
"resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.17.tgz",
"integrity": "sha512-pw3cZiaSlBrqRJUD/pHuMnKGsRTW6XJ255FrjDd3HC4QrqErCnfSQPmz+Sv4Qkelcvd9UGdAewyTqZdFwjLwOw==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-notifications": {
"version": "0.30.7",
"resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-0.30.7.tgz",
"integrity": "sha512-ExVxLIyD0SuoeO2icCbwtbqseblDP0/QK5fxCp4q2dk37DYd8yHoyIyxFBtqGE5eijuBOfIJ9nPs3bo9YW4b5g==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.7.2",
"@ide/backoff": "^1.0.0",
"abort-controller": "^3.0.0",
"assert": "^2.0.0",
"badgin": "^1.1.5",
"expo-application": "~6.1.2",
"expo-constants": "~17.1.2"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo-notifications/node_modules/@expo/image-utils": {
"version": "0.7.6",
"resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.7.6.tgz",
"integrity": "sha512-GKnMqC79+mo/1AFrmAcUcGfbsXXTRqOMNS1umebuevl3aaw+ztsYEFEiuNhHZW7PQ3Xs3URNT513ZxKhznDscw==",
"license": "MIT",
"dependencies": {
"@expo/spawn-async": "^1.7.2",
"chalk": "^4.0.0",
"getenv": "^2.0.0",
"jimp-compact": "0.16.1",
"parse-png": "^2.1.0",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"temp-dir": "~2.0.0",
"unique-string": "~2.0.0"
}
},
"node_modules/expo-secure-store": {
"version": "55.0.9",
"resolved": "https://registry.npmjs.org/expo-secure-store/-/expo-secure-store-55.0.9.tgz",
"integrity": "sha512-TIPGjM73LKlebpXwgAu/yL7lNWr6RQYmFw3vgYHOqLFYQMpsBqkQmopovbNX3c/0+RCE9KZlLAkcz8r6detILQ==",
"license": "MIT",
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo-server": {
"version": "55.0.6",
"resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.6.tgz",
"integrity": "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ==",
"license": "MIT",
"engines": {
"node": ">=20.16.0"
}
},
"node_modules/expo-status-bar": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.4.tgz",
"integrity": "sha512-BPDjUXKqv1F9j2YNGLRZfkBEZXIEEpqj+t81y4c+4fdSN3Pos7goIHXgcl2ozbKQLgKRZQyNZQtbUgh5UjHYUQ==",
"license": "MIT",
"dependencies": {
"react-native-is-edge-to-edge": "^1.2.1"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/cli": {
"version": "55.0.18",
"resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.18.tgz",
"integrity": "sha512-3sJwu8KvCvQIXBnhUlHgLBZBe+ZK4Da9R5rgI4znaowJavYWMqzRClLzyE6Kri66WVoMX7Q4HUVIh8prRlO0XA==",
"license": "MIT",
"dependencies": {
"@expo/code-signing-certificates": "^0.0.6",
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/devcert": "^1.2.1",
"@expo/env": "~2.1.1",
"@expo/image-utils": "^0.8.12",
"@expo/json-file": "^10.0.12",
"@expo/log-box": "55.0.7",
"@expo/metro": "~54.2.0",
"@expo/metro-config": "~55.0.11",
"@expo/osascript": "^2.4.2",
"@expo/package-manager": "^1.10.3",
"@expo/plist": "^0.5.2",
"@expo/prebuild-config": "^55.0.10",
"@expo/require-utils": "^55.0.3",
"@expo/router-server": "^55.0.11",
"@expo/schema-utils": "^55.0.2",
"@expo/spawn-async": "^1.7.2",
"@expo/ws-tunnel": "^1.0.1",
"@expo/xcpretty": "^4.4.0",
"@react-native/dev-middleware": "0.83.2",
"accepts": "^1.3.8",
"arg": "^5.0.2",
"better-opn": "~3.0.2",
"bplist-creator": "0.1.0",
"bplist-parser": "^0.3.1",
"chalk": "^4.0.0",
"ci-info": "^3.3.0",
"compression": "^1.7.4",
"connect": "^3.7.0",
"debug": "^4.3.4",
"dnssd-advertise": "^1.1.3",
"expo-server": "^55.0.6",
"fetch-nodeshim": "^0.4.6",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"lan-network": "^0.2.0",
"multitars": "^0.2.3",
"node-forge": "^1.3.3",
"npm-package-arg": "^11.0.0",
"ora": "^3.4.0",
"picomatch": "^4.0.3",
"pretty-format": "^29.7.0",
"progress": "^2.0.3",
"prompts": "^2.3.2",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"send": "^0.19.0",
"slugify": "^1.3.4",
"source-map-support": "~0.5.21",
"stacktrace-parser": "^0.1.10",
"structured-headers": "^0.4.1",
"terminal-link": "^2.1.1",
"toqr": "^0.1.1",
"wrap-ansi": "^7.0.0",
"ws": "^8.12.1",
"zod": "^3.25.76"
},
"bin": {
"expo-internal": "build/bin/cli"
},
"peerDependencies": {
"expo": "*",
"expo-router": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"expo-router": {
"optional": true
},
"react-native": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/prebuild-config": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.10.tgz",
"integrity": "sha512-AMylDld5G7YJGfEhEyXtgWRuBB83802QBoewF1vJ6NMDtufukuPhMJzOs9E4UXNsjLTaQcgT4yTWhsAWl7o1AQ==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"@expo/config-plugins": "~55.0.7",
"@expo/config-types": "^55.0.5",
"@expo/image-utils": "^0.8.12",
"@expo/json-file": "^10.0.12",
"@react-native/normalize-colors": "0.83.2",
"debug": "^4.3.1",
"resolve-from": "^5.0.0",
"semver": "^7.6.0",
"xml2js": "0.6.0"
},
"peerDependencies": {
"expo": "*"
}
},
"node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.11.tgz",
"integrity": "sha512-Kd8J1OOlFR00DZxn+1KfiQiXZtRut6cj8+ynqHJa7dtt/lTL4tGkYistqmVhpKJ6w886eRY5WivKy7o0ZBFkJA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"peerDependencies": {
"@expo/metro-runtime": "^55.0.6",
"expo": "*",
"expo-constants": "^55.0.9",
"expo-font": "^55.0.4",
"expo-router": "*",
"expo-server": "^55.0.6",
"react": "*",
"react-dom": "*",
"react-server-dom-webpack": "~19.0.1 || ~19.1.2 || ~19.2.1"
},
"peerDependenciesMeta": {
"@expo/metro-runtime": {
"optional": true
},
"expo-router": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-server-dom-webpack": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/log-box": {
"version": "55.0.7",
"resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.7.tgz",
"integrity": "sha512-m7V1k2vlMp4NOj3fopjOg4zl/ANXyTRF3HMTMep2GZAKsPiDzgOQ41nm8CaU50/HlDIGXlCObss07gOn20UpHQ==",
"license": "MIT",
"dependencies": {
"@expo/dom-webview": "^55.0.3",
"anser": "^1.4.9",
"stacktrace-parser": "^0.1.10"
},
"peerDependencies": {
"@expo/dom-webview": "^55.0.3",
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/log-box/node_modules/@expo/dom-webview": {
"version": "55.0.3",
"resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.3.tgz",
"integrity": "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/@expo/metro-config": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.11.tgz",
"integrity": "sha512-qGxq7RwWpj0zNvZO/e5aizKrOKYYBrVPShSbxPOVB1EXcexxTPTxnOe4pYFg/gKkLIJe0t3jSSF8IDWlGdaaOg==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.20.0",
"@babel/core": "^7.20.0",
"@babel/generator": "^7.20.5",
"@expo/config": "~55.0.10",
"@expo/env": "~2.1.1",
"@expo/json-file": "~10.0.12",
"@expo/metro": "~54.2.0",
"@expo/spawn-async": "^1.7.2",
"browserslist": "^4.25.0",
"chalk": "^4.1.0",
"debug": "^4.3.2",
"getenv": "^2.0.0",
"glob": "^13.0.0",
"hermes-parser": "^0.32.0",
"jsc-safe-url": "^0.2.4",
"lightningcss": "^1.30.1",
"picomatch": "^4.0.3",
"postcss": "~8.4.32",
"resolve-from": "^5.0.0"
},
"peerDependencies": {
"expo": "*"
},
"peerDependenciesMeta": {
"expo": {
"optional": true
}
}
},
"node_modules/expo/node_modules/@expo/vector-icons": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz",
"integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==",
"license": "MIT",
"peerDependencies": {
"expo-font": ">=14.0.4",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/babel-preset-expo": {
"version": "55.0.12",
"resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.12.tgz",
"integrity": "sha512-oR46ExGZpRijmPUsr0rFH5X4lR/mvwqJAFXJRLpynZcvyv2pHPTeGMNfd/p5oPMbdbaeMS6G+3k18p48u2Qjbw==",
"license": "MIT",
"dependencies": {
"@babel/generator": "^7.20.5",
"@babel/helper-module-imports": "^7.25.9",
"@babel/plugin-proposal-decorators": "^7.12.9",
"@babel/plugin-proposal-export-default-from": "^7.24.7",
"@babel/plugin-syntax-export-default-from": "^7.24.7",
"@babel/plugin-transform-class-static-block": "^7.27.1",
"@babel/plugin-transform-export-namespace-from": "^7.25.9",
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-object-rest-spread": "^7.24.7",
"@babel/plugin-transform-parameters": "^7.24.7",
"@babel/plugin-transform-private-methods": "^7.24.7",
"@babel/plugin-transform-private-property-in-object": "^7.24.7",
"@babel/plugin-transform-runtime": "^7.24.7",
"@babel/preset-react": "^7.22.15",
"@babel/preset-typescript": "^7.23.0",
"@react-native/babel-preset": "0.83.2",
"babel-plugin-react-compiler": "^1.0.0",
"babel-plugin-react-native-web": "~0.21.0",
"babel-plugin-syntax-hermes-parser": "^0.32.0",
"babel-plugin-transform-flow-enums": "^0.0.2",
"debug": "^4.3.4",
"resolve-from": "^5.0.0"
},
"peerDependencies": {
"@babel/runtime": "^7.20.0",
"expo": "*",
"expo-widgets": "^55.0.6",
"react-refresh": ">=0.14.0 <1.0.0"
},
"peerDependenciesMeta": {
"@babel/runtime": {
"optional": true
},
"expo": {
"optional": true
},
"expo-widgets": {
"optional": true
}
}
},
"node_modules/expo/node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/expo/node_modules/expo-asset": {
"version": "55.0.10",
"resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.10.tgz",
"integrity": "sha512-wxjNBKIaDyachq7oJgVlWVFzZ6SnNpJFJhkkcymXoTPt5O3XmDM+a6fT91xQQawCXTyZuCc1sNxKMetEofeYkg==",
"license": "MIT",
"dependencies": {
"@expo/image-utils": "^0.8.12",
"expo-constants": "~55.0.9"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-constants": {
"version": "55.0.9",
"resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.9.tgz",
"integrity": "sha512-iBiXjZeuU5S/8docQeNzsVvtDy4w0zlmXBpFEi1ypwugceEpdQQab65TVRbusXAcwpNVxCPMpNlDssYp0Pli2g==",
"license": "MIT",
"dependencies": {
"@expo/config": "~55.0.10",
"@expo/env": "~2.1.1"
},
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-file-system": {
"version": "55.0.11",
"resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.11.tgz",
"integrity": "sha512-KMUd6OY375J9WD79ZvjvCDZMveT7YfgiGWdi58/gfuTBsr14TRuoPk8RRQHAtc4UquzWViKcHwna9aPY7/XPpw==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-font": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz",
"integrity": "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg==",
"license": "MIT",
"dependencies": {
"fontfaceobserver": "^2.1.0"
},
"peerDependencies": {
"expo": "*",
"react": "*",
"react-native": "*"
}
},
"node_modules/expo/node_modules/expo-keep-awake": {
"version": "55.0.4",
"resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz",
"integrity": "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg==",
"license": "MIT",
"peerDependencies": {
"expo": "*",
"react": "*"
}
},
"node_modules/expo/node_modules/picomatch": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
"integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/expo/node_modules/ws": {
"version": "8.20.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz",
"integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/exponential-backoff": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz",
"integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==",
"license": "Apache-2.0"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
"license": "MIT"
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
"integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
"license": "MIT"
},
"node_modules/fb-dotslash": {
"version": "0.5.8",
"resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz",
"integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==",
"license": "(MIT OR Apache-2.0)",
"bin": {
"dotslash": "bin/dotslash"
},
"engines": {
"node": ">=20"
}
},
"node_modules/fb-watchman": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz",
"integrity": "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==",
"license": "Apache-2.0",
"dependencies": {
"bser": "2.1.1"
}
},
"node_modules/fetch-nodeshim": {
"version": "0.4.9",
"resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.9.tgz",
"integrity": "sha512-XIQWlB2A4RZ7NebXWGxS0uDMdvRHkiUDTghBVJKFg9yEOd45w/PP8cZANuPf2H08W6Cor3+2n7Q6TTZgAS3Fkw==",
"license": "MIT"
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/filter-obj": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz",
"integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/finalhandler": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz",
"integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"on-finished": "~2.3.0",
"parseurl": "~1.3.3",
"statuses": "~1.5.0",
"unpipe": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/find-up": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
"license": "MIT",
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/flow-enums-runtime": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/flow-enums-runtime/-/flow-enums-runtime-0.0.6.tgz",
"integrity": "sha512-3PYnM29RFXwvAN6Pc/scUfkI7RwhQ/xqyLUyPNlXUp9S40zI8nup9tUSrTLSVnWGBN38FNiGWbwZOB6uR4OGdw==",
"license": "MIT"
},
"node_modules/fontfaceobserver": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/fontfaceobserver/-/fontfaceobserver-2.3.0.tgz",
"integrity": "sha512-6FPvD/IVyT4ZlNe7Wcn5Fb/4ChigpucKYSvD6a+0iMoLn2inpo711eyIcKjmDtE5XNcgAkSH9uN/nfAeZzHEfg==",
"license": "BSD-2-Clause"
},
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
"integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
"license": "MIT",
"dependencies": {
"is-callable": "^1.2.7"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/foreground-child/node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fresh": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz",
"integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"license": "ISC"
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/generator-function": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
"integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/gensync": {
"version": "1.0.0-beta.2",
"resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
"integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
"license": "MIT",
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
"es-define-property": "^1.0.1",
"es-errors": "^1.3.0",
"es-object-atoms": "^1.1.1",
"function-bind": "^1.1.2",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-symbols": "^1.1.0",
"hasown": "^2.0.2",
"math-intrinsics": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/get-package-type": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
"es-object-atoms": "^1.0.0"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/getenv": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/getenv/-/getenv-2.0.0.tgz",
"integrity": "sha512-VilgtJj/ALgGY77fiLam5iD336eSWi96Q15JSAG1zi8NRBysm3LXKdGnHb4m5cuyxvOLQQKWpBZAT6ni4FI2iQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/glob": {
"version": "13.0.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz",
"integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"minimatch": "^10.2.2",
"minipass": "^7.1.3",
"path-scurry": "^2.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/gopd": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"license": "ISC"
},
"node_modules/has-flag": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/has-property-descriptors": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
"integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
"license": "MIT",
"dependencies": {
"es-define-property": "^1.0.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-symbols": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-tostringtag": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hasown": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/hermes-compiler": {
"version": "0.14.1",
"resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.1.tgz",
"integrity": "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA==",
"license": "MIT"
},
"node_modules/hermes-estree": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz",
"integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==",
"license": "MIT"
},
"node_modules/hermes-parser": {
"version": "0.32.0",
"resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz",
"integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==",
"license": "MIT",
"dependencies": {
"hermes-estree": "0.32.0"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
"integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
"license": "BSD-3-Clause",
"dependencies": {
"react-is": "^16.7.0"
}
},
"node_modules/hoist-non-react-statics/node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT"
},
"node_modules/hosted-git-info": {
"version": "7.0.2",
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
"integrity": "sha512-puUZAUKT5m8Zzvs72XWy3HtvVbTWljRE66cP60bxJzAqf2DgICo7lYTY2IHUmLnNpjYvw5bvmoHvPc0QO2a62w==",
"license": "ISC",
"dependencies": {
"lru-cache": "^10.0.1"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/hosted-git-info/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/http-errors": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz",
"integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==",
"license": "MIT",
"dependencies": {
"depd": "~2.0.0",
"inherits": "~2.0.4",
"setprototypeof": "~1.2.0",
"statuses": "~2.0.2",
"toidentifier": "~1.0.1"
},
"engines": {
"node": ">= 0.8"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/express"
}
},
"node_modules/http-errors/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/iceberg-js": {
"version": "0.8.1",
"resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz",
"integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==",
"license": "MIT",
"engines": {
"node": ">=20.0.0"
}
},
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
"integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
},
"node_modules/image-size": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/image-size/-/image-size-1.2.1.tgz",
"integrity": "sha512-rH+46sQJ2dlwfjfhCyNx5thzrv+dtmBIhPHk0zgRUukHzZ/kRueTJXoYYsclBaKcSMBWuGbOFXtioLpzTb5euw==",
"license": "MIT",
"dependencies": {
"queue": "6.0.2"
},
"bin": {
"image-size": "bin/image-size.js"
},
"engines": {
"node": ">=16.x"
}
},
"node_modules/imurmurhash": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
"license": "MIT",
"engines": {
"node": ">=0.8.19"
}
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
"license": "ISC",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/invariant": {
"version": "2.2.4",
"resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
"integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
"license": "MIT",
"dependencies": {
"loose-envify": "^1.0.0"
}
},
"node_modules/is-arguments": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz",
"integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-arrayish": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz",
"integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==",
"license": "MIT"
},
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
"integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-core-module": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-docker": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
"integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
"license": "MIT",
"bin": {
"is-docker": "cli.js"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-generator-function": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
"integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.4",
"generator-function": "^2.0.0",
"get-proto": "^1.0.1",
"has-tostringtag": "^1.0.2",
"safe-regex-test": "^1.1.0"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-nan": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz",
"integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.0",
"define-properties": "^1.1.3"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/is-regex": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
"integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2",
"hasown": "^2.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-typed-array": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
"integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
"license": "MIT",
"dependencies": {
"which-typed-array": "^1.1.16"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/istanbul-lib-coverage": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
"integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-instrument": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz",
"integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==",
"license": "BSD-3-Clause",
"dependencies": {
"@babel/core": "^7.12.3",
"@babel/parser": "^7.14.7",
"@istanbuljs/schema": "^0.1.2",
"istanbul-lib-coverage": "^3.2.0",
"semver": "^6.3.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/istanbul-lib-instrument/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jest-environment-node": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz",
"integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==",
"license": "MIT",
"dependencies": {
"@jest/environment": "^29.7.0",
"@jest/fake-timers": "^29.7.0",
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-mock": "^29.7.0",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-get-type": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz",
"integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==",
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-haste-map": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz",
"integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/graceful-fs": "^4.1.3",
"@types/node": "*",
"anymatch": "^3.0.3",
"fb-watchman": "^2.0.0",
"graceful-fs": "^4.2.9",
"jest-regex-util": "^29.6.3",
"jest-util": "^29.7.0",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"walker": "^1.0.8"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
},
"optionalDependencies": {
"fsevents": "^2.3.2"
}
},
"node_modules/jest-message-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz",
"integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.12.13",
"@jest/types": "^29.6.3",
"@types/stack-utils": "^2.0.0",
"chalk": "^4.0.0",
"graceful-fs": "^4.2.9",
"micromatch": "^4.0.4",
"pretty-format": "^29.7.0",
"slash": "^3.0.0",
"stack-utils": "^2.0.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-mock": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz",
"integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/node": "*",
"jest-util": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-regex-util": {
"version": "29.6.3",
"resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz",
"integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==",
"license": "MIT",
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-util": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz",
"integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"@types/node": "*",
"chalk": "^4.0.0",
"ci-info": "^3.2.0",
"graceful-fs": "^4.2.9",
"picomatch": "^2.2.3"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-util/node_modules/ci-info": {
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
"integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/sibiraj-s"
}
],
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/jest-validate": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz",
"integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==",
"license": "MIT",
"dependencies": {
"@jest/types": "^29.6.3",
"camelcase": "^6.2.0",
"chalk": "^4.0.0",
"jest-get-type": "^29.6.3",
"leven": "^3.1.0",
"pretty-format": "^29.7.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-worker": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz",
"integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"jest-util": "^29.7.0",
"merge-stream": "^2.0.0",
"supports-color": "^8.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/jest-worker/node_modules/supports-color": {
"version": "8.1.1",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
"integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/supports-color?sponsor=1"
}
},
"node_modules/jimp-compact": {
"version": "0.16.1",
"resolved": "https://registry.npmjs.org/jimp-compact/-/jimp-compact-0.16.1.tgz",
"integrity": "sha512-dZ6Ra7u1G8c4Letq/B5EzAxj4tLFHL+cGtdpR+PVm4yzPDj+lCk+AbivWt1eOM+ikzkowtyV7qSqX6qr3t71Ww==",
"license": "MIT"
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "3.14.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz",
"integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==",
"license": "MIT",
"dependencies": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
}
},
"node_modules/jsc-safe-url": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/jsc-safe-url/-/jsc-safe-url-0.2.4.tgz",
"integrity": "sha512-0wM3YBWtYePOjfyXQH5MWQ8H7sdk5EXSwZvmSLKk2RboVQ2Bu239jycHDz5J/8Blf3K0Qnoy2b6xD+z10MFB+Q==",
"license": "0BSD"
},
"node_modules/jsesc": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
"integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
"license": "MIT",
"bin": {
"jsesc": "bin/jsesc"
},
"engines": {
"node": ">=6"
}
},
"node_modules/json5": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
"integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
"license": "MIT",
"bin": {
"json5": "lib/cli.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/kleur": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz",
"integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/lan-network": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.0.tgz",
"integrity": "sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw==",
"license": "MIT",
"bin": {
"lan-network": "dist/lan-network-cli.js"
}
},
"node_modules/leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
"integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/lighthouse-logger": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz",
"integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==",
"license": "Apache-2.0",
"dependencies": {
"debug": "^2.6.9",
"marky": "^1.2.2"
}
},
"node_modules/lighthouse-logger/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/lighthouse-logger/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/lightningcss": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
"integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
"license": "MPL-2.0",
"dependencies": {
"detect-libc": "^2.0.3"
},
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
},
"optionalDependencies": {
"lightningcss-android-arm64": "1.32.0",
"lightningcss-darwin-arm64": "1.32.0",
"lightningcss-darwin-x64": "1.32.0",
"lightningcss-freebsd-x64": "1.32.0",
"lightningcss-linux-arm-gnueabihf": "1.32.0",
"lightningcss-linux-arm64-gnu": "1.32.0",
"lightningcss-linux-arm64-musl": "1.32.0",
"lightningcss-linux-x64-gnu": "1.32.0",
"lightningcss-linux-x64-musl": "1.32.0",
"lightningcss-win32-arm64-msvc": "1.32.0",
"lightningcss-win32-x64-msvc": "1.32.0"
}
},
"node_modules/lightningcss-android-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
"integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"android"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-arm64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
"integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-darwin-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
"integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-freebsd-x64": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
"integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"freebsd"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm-gnueabihf": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
"integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
"cpu": [
"arm"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
"integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-arm64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
"integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-gnu": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
"integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-linux-x64-musl": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
"integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-arm64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
"integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
"cpu": [
"arm64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lightningcss-win32-x64-msvc": {
"version": "1.32.0",
"resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
"integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
"cpu": [
"x64"
],
"license": "MPL-2.0",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/lines-and-columns": {
"version": "1.2.4",
"resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
"integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
"license": "MIT"
},
"node_modules/locate-path": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
"license": "MIT",
"dependencies": {
"p-locate": "^4.1.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==",
"license": "MIT"
},
"node_modules/lodash.throttle": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz",
"integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==",
"license": "MIT"
},
"node_modules/log-symbols": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
"integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
"license": "MIT",
"dependencies": {
"chalk": "^2.0.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/log-symbols/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/log-symbols/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/log-symbols/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/log-symbols/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
},
"bin": {
"loose-envify": "cli.js"
}
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
"integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
"license": "ISC",
"dependencies": {
"yallist": "^3.0.2"
}
},
"node_modules/makeerror": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz",
"integrity": "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==",
"license": "BSD-3-Clause",
"dependencies": {
"tmpl": "1.0.5"
}
},
"node_modules/marky": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz",
"integrity": "sha512-ocnPZQLNpvbedwTy9kNrQEsknEfgvcLMvOtz3sFeWApDq1MXH1TqkCIx58xlpESsfwQOnuBO9beyQuNGzVvuhQ==",
"license": "Apache-2.0"
},
"node_modules/math-intrinsics": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/memoize-one": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-5.2.1.tgz",
"integrity": "sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==",
"license": "MIT"
},
"node_modules/merge-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"license": "MIT"
},
"node_modules/metro": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz",
"integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==",
"license": "MIT",
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"@babel/types": "^7.25.2",
"accepts": "^1.3.7",
"chalk": "^4.0.0",
"ci-info": "^2.0.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"error-stack-parser": "^2.0.6",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"hermes-parser": "0.32.0",
"image-size": "^1.0.2",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.83.3",
"metro-cache": "0.83.3",
"metro-cache-key": "0.83.3",
"metro-config": "0.83.3",
"metro-core": "0.83.3",
"metro-file-map": "0.83.3",
"metro-resolver": "0.83.3",
"metro-runtime": "0.83.3",
"metro-source-map": "0.83.3",
"metro-symbolicate": "0.83.3",
"metro-transform-plugins": "0.83.3",
"metro-transform-worker": "0.83.3",
"mime-types": "^2.1.27",
"nullthrows": "^1.1.1",
"serialize-error": "^2.1.0",
"source-map": "^0.5.6",
"throat": "^5.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"bin": {
"metro": "src/cli.js"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-babel-transformer": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz",
"integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"hermes-parser": "0.32.0",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-cache": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz",
"integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==",
"license": "MIT",
"dependencies": {
"exponential-backoff": "^3.1.1",
"flow-enums-runtime": "^0.0.6",
"https-proxy-agent": "^7.0.5",
"metro-core": "0.83.3"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-cache-key": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz",
"integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-config": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz",
"integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==",
"license": "MIT",
"dependencies": {
"connect": "^3.6.5",
"flow-enums-runtime": "^0.0.6",
"jest-validate": "^29.7.0",
"metro": "0.83.3",
"metro-cache": "0.83.3",
"metro-core": "0.83.3",
"metro-runtime": "0.83.3",
"yaml": "^2.6.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-core": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz",
"integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"lodash.throttle": "^4.1.1",
"metro-resolver": "0.83.3"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-file-map": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz",
"integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==",
"license": "MIT",
"dependencies": {
"debug": "^4.4.0",
"fb-watchman": "^2.0.0",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"nullthrows": "^1.1.1",
"walker": "^1.0.7"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-minify-terser": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz",
"integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"terser": "^5.15.0"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-resolver": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz",
"integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-runtime": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz",
"integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.25.0",
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-source-map": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz",
"integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==",
"license": "MIT",
"dependencies": {
"@babel/traverse": "^7.25.3",
"@babel/traverse--for-generate-function-map": "npm:@babel/traverse@^7.25.3",
"@babel/types": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"metro-symbolicate": "0.83.3",
"nullthrows": "^1.1.1",
"ob1": "0.83.3",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-symbolicate": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz",
"integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"invariant": "^2.2.4",
"metro-source-map": "0.83.3",
"nullthrows": "^1.1.1",
"source-map": "^0.5.6",
"vlq": "^1.0.0"
},
"bin": {
"metro-symbolicate": "src/index.js"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-transform-plugins": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz",
"integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"flow-enums-runtime": "^0.0.6",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/metro-transform-worker": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz",
"integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==",
"license": "MIT",
"dependencies": {
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/types": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"metro": "0.83.3",
"metro-babel-transformer": "0.83.3",
"metro-cache": "0.83.3",
"metro-cache-key": "0.83.3",
"metro-minify-terser": "0.83.3",
"metro-source-map": "0.83.3",
"metro-transform-plugins": "0.83.3",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/micromatch": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"license": "MIT",
"dependencies": {
"braces": "^3.0.3",
"picomatch": "^2.3.1"
},
"engines": {
"node": ">=8.6"
}
},
"node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
"integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
"license": "MIT",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mimic-fn": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
"integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/minimatch": {
"version": "10.2.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
"integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"brace-expansion": "^5.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"license": "MIT",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/multitars": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.4.tgz",
"integrity": "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg==",
"license": "MIT"
},
"node_modules/mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
"integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0",
"object-assign": "^4.0.1",
"thenify-all": "^1.0.0"
}
},
"node_modules/nanoid": {
"version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"bin": {
"nanoid": "bin/nanoid.cjs"
},
"engines": {
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
}
},
"node_modules/negotiator": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
"integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/node-forge": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/node-forge/-/node-forge-1.3.3.tgz",
"integrity": "sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==",
"license": "(BSD-3-Clause OR GPL-2.0)",
"engines": {
"node": ">= 6.13.0"
}
},
"node_modules/node-int64": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz",
"integrity": "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==",
"license": "MIT"
},
"node_modules/node-releases": {
"version": "2.0.36",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
"integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
"license": "MIT"
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/npm-package-arg": {
"version": "11.0.3",
"resolved": "https://registry.npmjs.org/npm-package-arg/-/npm-package-arg-11.0.3.tgz",
"integrity": "sha512-sHGJy8sOC1YraBywpzQlIKBE4pBbGbiF95U6Auspzyem956E0+FtDtsx1ZxlOJkQCZ1AFXAY/yuvtFYrOxF+Bw==",
"license": "ISC",
"dependencies": {
"hosted-git-info": "^7.0.0",
"proc-log": "^4.0.0",
"semver": "^7.3.5",
"validate-npm-package-name": "^5.0.0"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
}
},
"node_modules/nullthrows": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/nullthrows/-/nullthrows-1.1.1.tgz",
"integrity": "sha512-2vPPEi+Z7WqML2jZYddDIfy5Dqb0r2fze2zTxNNknZaFpVHU3mFB3R+DWeJWGVx0ecvttSGlJTI+WG+8Z4cDWw==",
"license": "MIT"
},
"node_modules/ob1": {
"version": "0.83.3",
"resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz",
"integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==",
"license": "MIT",
"dependencies": {
"flow-enums-runtime": "^0.0.6"
},
"engines": {
"node": ">=20.19.4"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/object-is": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz",
"integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.7",
"define-properties": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/object.assign": {
"version": "4.1.7",
"resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
"integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
"license": "MIT",
"dependencies": {
"call-bind": "^1.0.8",
"call-bound": "^1.0.3",
"define-properties": "^1.2.1",
"es-object-atoms": "^1.0.0",
"has-symbols": "^1.1.0",
"object-keys": "^1.1.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
"integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/on-headers": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz",
"integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
"integrity": "sha512-oyyPpiMaKARvvcgip+JV+7zci5L8D1W9RZIz2l1o08AM3pfspitVWnPt3mzHcBPp12oYMTy0pqrFs/C+m3EwsQ==",
"license": "MIT",
"dependencies": {
"mimic-fn": "^1.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/open": {
"version": "7.4.2",
"resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
"integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
"license": "MIT",
"dependencies": {
"is-docker": "^2.0.0",
"is-wsl": "^2.1.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/ora": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/ora/-/ora-3.4.0.tgz",
"integrity": "sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg==",
"license": "MIT",
"dependencies": {
"chalk": "^2.4.2",
"cli-cursor": "^2.1.0",
"cli-spinners": "^2.0.0",
"log-symbols": "^2.2.0",
"strip-ansi": "^5.2.0",
"wcwidth": "^1.0.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/ansi-regex": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
"integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"license": "MIT",
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"license": "MIT",
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/ora/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"license": "MIT"
},
"node_modules/ora/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"license": "MIT",
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/ora/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ora/node_modules/strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^4.1.0"
},
"engines": {
"node": ">=6"
}
},
"node_modules/ora/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/p-limit": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
"license": "MIT",
"dependencies": {
"p-try": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/p-locate": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
"license": "MIT",
"dependencies": {
"p-limit": "^2.2.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"license": "BlueOak-1.0.0"
},
"node_modules/parse-png": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/parse-png/-/parse-png-2.1.0.tgz",
"integrity": "sha512-Nt/a5SfCLiTnQAjx3fHlqp8hRgTL3z7kTQZzvIMS9uCAepnCyjpdEc6M/sz69WqMBdaDBw9sF1F1UaHROYzGkQ==",
"license": "MIT",
"dependencies": {
"pngjs": "^3.3.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/parseurl": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
"integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/path-parse": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
"license": "MIT"
},
"node_modules/path-scurry": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz",
"integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^11.0.0",
"minipass": "^7.1.2"
},
"engines": {
"node": "18 || 20 || >=22"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "11.2.7",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
"integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": "20 || >=22"
}
},
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"license": "ISC"
},
"node_modules/picomatch": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pirates": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
"integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/plist": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/plist/-/plist-3.1.0.tgz",
"integrity": "sha512-uysumyrvkUX0rX/dEVqt8gC3sTBzd4zoWfLeS29nb53imdaXVvLINYXTI2GNqzaMuvacNx4uJQ8+b3zXR0pkgQ==",
"license": "MIT",
"dependencies": {
"@xmldom/xmldom": "^0.8.8",
"base64-js": "^1.5.1",
"xmlbuilder": "^15.1.1"
},
"engines": {
"node": ">=10.4.0"
}
},
"node_modules/pngjs": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz",
"integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/possible-typed-array-names": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
"integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
}
},
"node_modules/postcss": {
"version": "8.4.49",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.49.tgz",
"integrity": "sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/postcss/"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/postcss"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"nanoid": "^3.3.7",
"picocolors": "^1.1.1",
"source-map-js": "^1.2.1"
},
"engines": {
"node": "^10 || ^12 || >=14"
}
},
"node_modules/pretty-format": {
"version": "29.7.0",
"resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz",
"integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==",
"license": "MIT",
"dependencies": {
"@jest/schemas": "^29.6.3",
"ansi-styles": "^5.0.0",
"react-is": "^18.0.0"
},
"engines": {
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
}
},
"node_modules/pretty-format/node_modules/ansi-styles": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
"integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/proc-log": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/proc-log/-/proc-log-4.2.0.tgz",
"integrity": "sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/progress": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/promise": {
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz",
"integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==",
"license": "MIT",
"dependencies": {
"asap": "~2.0.6"
}
},
"node_modules/prompts": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz",
"integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==",
"license": "MIT",
"dependencies": {
"kleur": "^3.0.3",
"sisteransi": "^1.0.5"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/query-string": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz",
"integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==",
"license": "MIT",
"dependencies": {
"decode-uri-component": "^0.2.2",
"filter-obj": "^1.1.0",
"split-on-first": "^1.0.0",
"strict-uri-encode": "^2.0.0"
},
"engines": {
"node": ">=6"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/queue": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/queue/-/queue-6.0.2.tgz",
"integrity": "sha512-iHZWu+q3IdFZFX36ro/lKBkSvfkztY5Y7HMiPlOUjhupPcG2JMfst2KKEpu5XndviX/3UhFbRngUPNKtgvtZiA==",
"license": "MIT",
"dependencies": {
"inherits": "~2.0.3"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
"integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/react": {
"version": "19.2.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.0.tgz",
"integrity": "sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react-devtools-core": {
"version": "6.1.5",
"resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz",
"integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==",
"license": "MIT",
"dependencies": {
"shell-quote": "^1.6.1",
"ws": "^7"
}
},
"node_modules/react-freeze": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.4.tgz",
"integrity": "sha512-r4F0Sec0BLxWicc7HEyo2x3/2icUTrRmDjaaRyzzn+7aDyFZliszMDOgLVwSnQnYENOlL1o569Ze2HZefk8clA==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"peerDependencies": {
"react": ">=17.0.0"
}
},
"node_modules/react-is": {
"version": "18.3.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
"integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
"license": "MIT"
},
"node_modules/react-native": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.2.tgz",
"integrity": "sha512-ZDma3SLkRN2U2dg0/EZqxNBAx4of/oTnPjXAQi299VLq2gdnbZowGy9hzqv+O7sTA62g+lM1v+2FM5DUnJ/6hg==",
"license": "MIT",
"dependencies": {
"@jest/create-cache-key-function": "^29.7.0",
"@react-native/assets-registry": "0.83.2",
"@react-native/codegen": "0.83.2",
"@react-native/community-cli-plugin": "0.83.2",
"@react-native/gradle-plugin": "0.83.2",
"@react-native/js-polyfills": "0.83.2",
"@react-native/normalize-colors": "0.83.2",
"@react-native/virtualized-lists": "0.83.2",
"abort-controller": "^3.0.0",
"anser": "^1.4.9",
"ansi-regex": "^5.0.0",
"babel-jest": "^29.7.0",
"babel-plugin-syntax-hermes-parser": "0.32.0",
"base64-js": "^1.5.1",
"commander": "^12.0.0",
"flow-enums-runtime": "^0.0.6",
"glob": "^7.1.1",
"hermes-compiler": "0.14.1",
"invariant": "^2.2.4",
"jest-environment-node": "^29.7.0",
"memoize-one": "^5.0.0",
"metro-runtime": "^0.83.3",
"metro-source-map": "^0.83.3",
"nullthrows": "^1.1.1",
"pretty-format": "^29.7.0",
"promise": "^8.3.0",
"react-devtools-core": "^6.1.5",
"react-refresh": "^0.14.0",
"regenerator-runtime": "^0.13.2",
"scheduler": "0.27.0",
"semver": "^7.1.3",
"stacktrace-parser": "^0.1.10",
"whatwg-fetch": "^3.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"bin": {
"react-native": "cli.js"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@types/react": "^19.1.1",
"react": "^19.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-native-gesture-handler": {
"version": "2.30.0",
"resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.0.tgz",
"integrity": "sha512-5YsnKHGa0X9C8lb5oCnKm0fLUPM6CRduvUUw2Bav4RIj/C3HcFh4RIUnF8wgG6JQWCL1//gRx4v+LVWgcIQdGA==",
"license": "MIT",
"dependencies": {
"@egjs/hammerjs": "^2.0.17",
"hoist-non-react-statics": "^3.3.0",
"invariant": "^2.2.4"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-is-edge-to-edge": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.3.1.tgz",
"integrity": "sha512-NIXU/iT5+ORyCc7p0z2nnlkouYKX425vuU1OEm6bMMtWWR9yvb+Xg5AZmImTKoF9abxCPqrKC3rOZsKzUYgYZA==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-maps": {
"version": "1.27.2",
"resolved": "https://registry.npmjs.org/react-native-maps/-/react-native-maps-1.27.2.tgz",
"integrity": "sha512-VKr+xZ2RZGHHJlY6KhlafvGSmK0dq/tUu5uhfJ7K9rwN5pUdubdugzMKGDU/16lXmQSg7xbClKhRctj3Pm5F5g==",
"license": "MIT",
"dependencies": {
"@types/geojson": "^7946.0.13"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"react": ">= 18.3.1",
"react-native": ">= 0.76.0",
"react-native-web": ">= 0.11"
},
"peerDependenciesMeta": {
"react-native-web": {
"optional": true
}
}
},
"node_modules/react-native-safe-area-context": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.7.0.tgz",
"integrity": "sha512-/9/MtQz8ODphjsLdZ+GZAIcC/RtoqW9EeShf7Uvnfgm/pzYrJ75y3PV/J1wuAV1T5Dye5ygq4EAW20RoBq0ABQ==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native-screens": {
"version": "4.23.0",
"resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.23.0.tgz",
"integrity": "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw==",
"license": "MIT",
"dependencies": {
"react-freeze": "^1.0.0",
"warn-once": "^0.1.0"
},
"peerDependencies": {
"react": "*",
"react-native": "*"
}
},
"node_modules/react-native/node_modules/@react-native/virtualized-lists": {
"version": "0.83.2",
"resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.2.tgz",
"integrity": "sha512-N7mRjHLW/+KWxMp9IHRWyE3VIkeG1m3PnZJAGEFLCN8VFb7e4VfI567o7tE/HYcdcXCylw+Eqhlciz8gDeQ71g==",
"license": "MIT",
"dependencies": {
"invariant": "^2.2.4",
"nullthrows": "^1.1.1"
},
"engines": {
"node": ">= 20.19.4"
},
"peerDependencies": {
"@types/react": "^19.2.0",
"react": "*",
"react-native": "*"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/react-native/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/react-native/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/react-native/node_modules/commander": {
"version": "12.1.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz",
"integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/react-native/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/react-native/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/react-refresh": {
"version": "0.14.2",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.2.tgz",
"integrity": "sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
"integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==",
"license": "MIT"
},
"node_modules/regenerate-unicode-properties": {
"version": "10.2.2",
"resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-10.2.2.tgz",
"integrity": "sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/regexpu-core": {
"version": "6.4.0",
"resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-6.4.0.tgz",
"integrity": "sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==",
"license": "MIT",
"dependencies": {
"regenerate": "^1.4.2",
"regenerate-unicode-properties": "^10.2.2",
"regjsgen": "^0.8.0",
"regjsparser": "^0.13.0",
"unicode-match-property-ecmascript": "^2.0.0",
"unicode-match-property-value-ecmascript": "^2.2.1"
},
"engines": {
"node": ">=4"
}
},
"node_modules/regjsgen": {
"version": "0.8.0",
"resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.8.0.tgz",
"integrity": "sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==",
"license": "MIT"
},
"node_modules/regjsparser": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.0.tgz",
"integrity": "sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==",
"license": "BSD-2-Clause",
"dependencies": {
"jsesc": "~3.1.0"
},
"bin": {
"regjsparser": "bin/parser"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
"integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/resolve": {
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
"path-parse": "^1.0.7",
"supports-preserve-symlinks-flag": "^1.0.0"
},
"bin": {
"resolve": "bin/resolve"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/resolve-from": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz",
"integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/resolve-workspace-root": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/resolve-workspace-root/-/resolve-workspace-root-2.0.1.tgz",
"integrity": "sha512-nR23LHAvaI6aHtMg6RWoaHpdR4D881Nydkzi2CixINyg9T00KgaJdJI6Vwty+Ps8WLxZHuxsS0BseWjxSA4C+w==",
"license": "MIT"
},
"node_modules/restore-cursor": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
"integrity": "sha512-6IzJLuGi4+R14vwagDHX+JrXmPVtPpn4mffDJ1UdR7/Edm87fl6yi8mMBIVvFtJaNTUvjughmW4hwLhRG7gC1Q==",
"license": "MIT",
"dependencies": {
"onetime": "^2.0.0",
"signal-exit": "^3.0.2"
},
"engines": {
"node": ">=4"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"license": "ISC",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/rimraf/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/rimraf/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-regex-test": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
"integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
"license": "MIT",
"dependencies": {
"call-bound": "^1.0.2",
"es-errors": "^1.3.0",
"is-regex": "^1.2.1"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sax": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.6.0.tgz",
"integrity": "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA==",
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=11.0.0"
}
},
"node_modules/scheduler": {
"version": "0.27.0",
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/send": {
"version": "0.19.2",
"resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz",
"integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==",
"license": "MIT",
"dependencies": {
"debug": "2.6.9",
"depd": "2.0.0",
"destroy": "1.2.0",
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "~0.5.2",
"http-errors": "~2.0.1",
"mime": "1.6.0",
"ms": "2.1.3",
"on-finished": "~2.4.1",
"range-parser": "~1.2.1",
"statuses": "~2.0.2"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/send/node_modules/debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"license": "MIT",
"dependencies": {
"ms": "2.0.0"
}
},
"node_modules/send/node_modules/debug/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/on-finished": {
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz",
"integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==",
"license": "MIT",
"dependencies": {
"ee-first": "1.1.1"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/statuses": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz",
"integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/serialize-error": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-2.1.0.tgz",
"integrity": "sha512-ghgmKt5o4Tly5yEG/UJp8qTd0AN7Xalw4XBtDEKP655B699qMEtra1WlXeE6WIvdEG481JvRxULKsInq/iNysw==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/serve-static": {
"version": "1.16.3",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz",
"integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==",
"license": "MIT",
"dependencies": {
"encodeurl": "~2.0.0",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
"send": "~0.19.1"
},
"engines": {
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-function-length": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
"integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
"license": "MIT",
"dependencies": {
"define-data-property": "^1.1.4",
"es-errors": "^1.3.0",
"function-bind": "^1.1.2",
"get-intrinsic": "^1.2.4",
"gopd": "^1.0.1",
"has-property-descriptors": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
}
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==",
"license": "ISC"
},
"node_modules/sf-symbols-typescript": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.2.0.tgz",
"integrity": "sha512-TPbeg0b7ylrswdGCji8FRGFAKuqbpQlLbL8SOle3j1iHSs5Ob5mhvMAxWN2UItOjgALAB5Zp3fmMfj8mbWvXKw==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"license": "MIT",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/shell-quote": {
"version": "1.8.3",
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz",
"integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"license": "ISC"
},
"node_modules/simple-plist": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
"integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
"license": "MIT",
"dependencies": {
"bplist-creator": "0.1.0",
"bplist-parser": "0.3.1",
"plist": "^3.0.5"
}
},
"node_modules/simple-swizzle": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz",
"integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==",
"license": "MIT",
"dependencies": {
"is-arrayish": "^0.3.1"
}
},
"node_modules/sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
"integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==",
"license": "MIT"
},
"node_modules/slash": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
"integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/slugify": {
"version": "1.6.8",
"resolved": "https://registry.npmjs.org/slugify/-/slugify-1.6.8.tgz",
"integrity": "sha512-HVk9X1E0gz3mSpoi60h/saazLKXKaZThMLU3u/aNwoYn8/xQyX2MGxL0ui2eaokkD7tF+Zo+cKTHUbe1mmmGzA==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/source-map-support": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz",
"integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==",
"license": "MIT",
"dependencies": {
"buffer-from": "^1.0.0",
"source-map": "^0.6.0"
}
},
"node_modules/source-map-support/node_modules/source-map": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
"license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/split-on-first": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz",
"integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/stack-utils": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
"integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
"license": "MIT",
"dependencies": {
"escape-string-regexp": "^2.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/stack-utils/node_modules/escape-string-regexp": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
"integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/stackframe": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz",
"integrity": "sha512-oeVtt7eWQS+Na6F//S4kJ2K2VbRlS9D43mAlMyVpVWovy9o+jfgH8O9agzANzaiLjclA0oYzUXEM4PurhSUChw==",
"license": "MIT"
},
"node_modules/stacktrace-parser": {
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/stacktrace-parser/-/stacktrace-parser-0.1.11.tgz",
"integrity": "sha512-WjlahMgHmCJpqzU8bIBy4qtsZdU9lRlcZE3Lvyej6t4tuOuv1vk57OW3MBrj6hXBFx/nNoC9MPMTcr5YA7NQbg==",
"license": "MIT",
"dependencies": {
"type-fest": "^0.7.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/stream-buffers": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/stream-buffers/-/stream-buffers-2.2.0.tgz",
"integrity": "sha512-uyQK/mx5QjHun80FLJTfaWE7JtwfRMKBLkMne6udYOmvH0CawotVa7TfgYHzAnpphn4+TweIx1QKMnRIbipmUg==",
"license": "Unlicense",
"engines": {
"node": ">= 0.10.0"
}
},
"node_modules/strict-uri-encode": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz",
"integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/structured-headers": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/structured-headers/-/structured-headers-0.4.1.tgz",
"integrity": "sha512-0MP/Cxx5SzeeZ10p/bZI0S6MpgD+yxAhi1BOQ34jgnMXsCq3j1t6tQnZu+KdlL7dvJTLT3g9xN8tl10TqgFMcg==",
"license": "MIT"
},
"node_modules/sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
"integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==",
"license": "MIT",
"dependencies": {
"@jridgewell/gen-mapping": "^0.3.2",
"commander": "^4.0.0",
"glob": "^10.3.10",
"lines-and-columns": "^1.1.6",
"mz": "^2.7.0",
"pirates": "^4.0.1",
"ts-interface-checker": "^0.1.9"
},
"bin": {
"sucrase": "bin/sucrase",
"sucrase-node": "bin/sucrase-node"
},
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/sucrase/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/sucrase/node_modules/brace-expansion": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
"integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/sucrase/node_modules/commander": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
"integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
"license": "MIT",
"engines": {
"node": ">= 6"
}
},
"node_modules/sucrase/node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"license": "ISC"
},
"node_modules/sucrase/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/sucrase/node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-hyperlinks": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz",
"integrity": "sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==",
"license": "MIT",
"dependencies": {
"has-flag": "^4.0.0",
"supports-color": "^7.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/tagged-tag": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tagged-tag/-/tagged-tag-1.0.0.tgz",
"integrity": "sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==",
"license": "MIT",
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/temp-dir": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/temp-dir/-/temp-dir-2.0.0.tgz",
"integrity": "sha512-aoBAniQmmwtcKp/7BzsH8Cxzv8OL736p7v1ihGb5e9DJ9kTwGWHrQrVB5+lfVDzfGrdRzXch+ig7LHaY1JTOrg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/terminal-link": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz",
"integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==",
"license": "MIT",
"dependencies": {
"ansi-escapes": "^4.2.1",
"supports-hyperlinks": "^2.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/terser": {
"version": "5.46.1",
"resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz",
"integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==",
"license": "BSD-2-Clause",
"dependencies": {
"@jridgewell/source-map": "^0.3.3",
"acorn": "^8.15.0",
"commander": "^2.20.0",
"source-map-support": "~0.5.20"
},
"bin": {
"terser": "bin/terser"
},
"engines": {
"node": ">=10"
}
},
"node_modules/terser/node_modules/commander": {
"version": "2.20.3",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
"integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
"license": "MIT"
},
"node_modules/test-exclude": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz",
"integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==",
"license": "ISC",
"dependencies": {
"@istanbuljs/schema": "^0.1.2",
"glob": "^7.1.4",
"minimatch": "^3.0.4"
},
"engines": {
"node": ">=8"
}
},
"node_modules/test-exclude/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"license": "MIT"
},
"node_modules/test-exclude/node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/test-exclude/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"license": "ISC",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/test-exclude/node_modules/minimatch": {
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/thenify": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
"integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
"license": "MIT",
"dependencies": {
"any-promise": "^1.0.0"
}
},
"node_modules/thenify-all": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
"integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
"license": "MIT",
"dependencies": {
"thenify": ">= 3.1.0 < 4"
},
"engines": {
"node": ">=0.8"
}
},
"node_modules/throat": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz",
"integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==",
"license": "MIT"
},
"node_modules/tmpl": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz",
"integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==",
"license": "BSD-3-Clause"
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"license": "MIT",
"engines": {
"node": ">=0.6"
}
},
"node_modules/toqr": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz",
"integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==",
"license": "MIT"
},
"node_modules/ts-interface-checker": {
"version": "0.1.13",
"resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
"integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
"license": "Apache-2.0"
},
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/type-fest": {
"version": "0.7.1",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.7.1.tgz",
"integrity": "sha512-Ne2YiiGN8bmrmJJEuTWTLJR32nh/JdL1+PSicowtNb0WFpn59GK8/lfD61bVtzguz7b3PBt74nxpv/Pw5po5Rg==",
"license": "(MIT OR CC0-1.0)",
"engines": {
"node": ">=8"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"devOptional": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz",
"integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
"license": "MIT"
},
"node_modules/unicode-canonical-property-names-ecmascript": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.1.tgz",
"integrity": "sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-ecmascript": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz",
"integrity": "sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==",
"license": "MIT",
"dependencies": {
"unicode-canonical-property-names-ecmascript": "^2.0.0",
"unicode-property-aliases-ecmascript": "^2.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-match-property-value-ecmascript": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.2.1.tgz",
"integrity": "sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unicode-property-aliases-ecmascript": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.2.0.tgz",
"integrity": "sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==",
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/unique-string": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/unique-string/-/unique-string-2.0.0.tgz",
"integrity": "sha512-uNaeirEPvpZWSgzwsPGtU2zVSTrn/8L5q/IexZmH0eH6SA73CmAA5U4GwORTxQAZs95TAXLNqeLoPPNO5gZfWg==",
"license": "MIT",
"dependencies": {
"crypto-random-string": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/update-browserslist-db": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
"integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
"funding": [
{
"type": "opencollective",
"url": "https://opencollective.com/browserslist"
},
{
"type": "tidelift",
"url": "https://tidelift.com/funding/github/npm/browserslist"
},
{
"type": "github",
"url": "https://github.com/sponsors/ai"
}
],
"license": "MIT",
"dependencies": {
"escalade": "^3.2.0",
"picocolors": "^1.1.1"
},
"bin": {
"update-browserslist-db": "cli.js"
},
"peerDependencies": {
"browserslist": ">= 4.21.0"
}
},
"node_modules/use-latest-callback": {
"version": "0.2.6",
"resolved": "https://registry.npmjs.org/use-latest-callback/-/use-latest-callback-0.2.6.tgz",
"integrity": "sha512-FvRG9i1HSo0wagmX63Vrm8SnlUU3LMM3WyZkQ76RnslpBrX694AdG4A0zQBx2B3ZifFA0yv/BaEHGBnEax5rZg==",
"license": "MIT",
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/use-sync-external-store": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz",
"integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/util": {
"version": "0.12.5",
"resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz",
"integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"is-arguments": "^1.0.4",
"is-generator-function": "^1.0.7",
"is-typed-array": "^1.1.3",
"which-typed-array": "^1.1.2"
}
},
"node_modules/utils-merge": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==",
"license": "MIT",
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz",
"integrity": "sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg==",
"license": "MIT",
"bin": {
"uuid": "dist/bin/uuid"
}
},
"node_modules/validate-npm-package-name": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/validate-npm-package-name/-/validate-npm-package-name-5.0.1.tgz",
"integrity": "sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==",
"license": "ISC",
"engines": {
"node": "^14.17.0 || ^16.13.0 || >=18.0.0"
}
},
"node_modules/vary": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz",
"integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/vlq": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/vlq/-/vlq-1.0.1.tgz",
"integrity": "sha512-gQpnTgkubC6hQgdIcRdYGDSDc+SaujOdyesZQMv6JlfQee/9Mp0Qhnys6WxDWvQnL5WZdT7o2Ul187aSt0Rq+w==",
"license": "MIT"
},
"node_modules/walker": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.8.tgz",
"integrity": "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==",
"license": "Apache-2.0",
"dependencies": {
"makeerror": "1.0.12"
}
},
"node_modules/warn-once": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/warn-once/-/warn-once-0.1.1.tgz",
"integrity": "sha512-VkQZJbO8zVImzYFteBXvBOZEl1qL175WH8VmZcxF2fZAoudNhNDvHi+doCaAEdU2l2vtcIwa2zn0QK5+I1HQ3Q==",
"license": "MIT"
},
"node_modules/wcwidth": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz",
"integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==",
"license": "MIT",
"dependencies": {
"defaults": "^1.0.3"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.20",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.20.tgz",
"integrity": "sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==",
"license": "MIT"
},
"node_modules/whatwg-url-minimum": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.1.tgz",
"integrity": "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA==",
"license": "MIT"
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"license": "ISC",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/which-typed-array": {
"version": "1.1.22",
"resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz",
"integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==",
"license": "MIT",
"dependencies": {
"available-typed-arrays": "^1.0.7",
"call-bind": "^1.0.9",
"call-bound": "^1.0.4",
"for-each": "^0.3.5",
"get-proto": "^1.0.1",
"gopd": "^1.2.0",
"has-tostringtag": "^1.0.2"
},
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/write-file-atomic": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz",
"integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==",
"license": "ISC",
"dependencies": {
"imurmurhash": "^0.1.4",
"signal-exit": "^3.0.7"
},
"engines": {
"node": "^12.13.0 || ^14.15.0 || >=16.0.0"
}
},
"node_modules/ws": {
"version": "7.5.10",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
"license": "MIT",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/xcode": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/xcode/-/xcode-3.0.1.tgz",
"integrity": "sha512-kCz5k7J7XbJtjABOvkc5lJmkiDh8VhjVCGNiqdKCscmVpdVUpEAyXv1xmCLkQJ5dsHqx3IPO4XW+NTDhU/fatA==",
"license": "Apache-2.0",
"dependencies": {
"simple-plist": "^1.1.0",
"uuid": "^7.0.3"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/xml2js": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.6.0.tgz",
"integrity": "sha512-eLTh0kA8uHceqesPqSE+VvO1CDDJWMwlQfB6LuN6T8w6MaDJ8Txm8P7s5cHD0miF0V+GGTZrDQfxPZQVsur33w==",
"license": "MIT",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~11.0.0"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/xml2js/node_modules/xmlbuilder": {
"version": "11.0.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
"license": "MIT",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xmlbuilder": {
"version": "15.1.1",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-15.1.1.tgz",
"integrity": "sha512-yMqGBqtXyeN1e3TGYvgNgDVZ3j84W4cwkOXQswghol6APgZWaff9lnbvN7MHYJOiXsvGPXtjTYJEiC9J2wv9Eg==",
"license": "MIT",
"engines": {
"node": ">=8.0"
}
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
"integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
"license": "ISC"
},
"node_modules/yaml": {
"version": "2.8.3",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz",
"integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
},
"engines": {
"node": ">= 14.6"
},
"funding": {
"url": "https://github.com/sponsors/eemeli"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/zod": {
"version": "3.25.76",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz",
"integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/zustand": {
"version": "5.0.12",
"resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
"integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
"license": "MIT",
"engines": {
"node": ">=12.20.0"
},
"peerDependencies": {
"@types/react": ">=18.0.0",
"immer": ">=9.0.6",
"react": ">=18.0.0",
"use-sync-external-store": ">=1.2.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"immer": {
"optional": true
},
"react": {
"optional": true
},
"use-sync-external-store": {
"optional": true
}
}
},
"node_modules/zxing-wasm": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/zxing-wasm/-/zxing-wasm-3.0.1.tgz",
"integrity": "sha512-3CLj6iaGkpqPWXAB4pIWkFOR63MwqGekpMzaROFKto4dFowiPmLlC56KoMoOSXzqOCOpI5DAvMdB8ku2va6fUg==",
"license": "MIT",
"dependencies": {
"@types/emscripten": "^1.41.5",
"type-fest": "^5.4.4"
},
"peerDependencies": {
"@types/emscripten": ">=1.39.6"
}
},
"node_modules/zxing-wasm/node_modules/type-fest": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.5.0.tgz",
"integrity": "sha512-PlBfpQwiUvGViBNX84Yxwjsdhd1TUlXr6zjX7eoirtCPIr08NAmxwa+fcYBTeRQxHo9YC9wwF3m9i700sHma8g==",
"license": "(MIT OR CC0-1.0)",
"dependencies": {
"tagged-tag": "^1.0.0"
},
"engines": {
"node": ">=20"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
}
}
}
+29 -59
View File
@@ -1,68 +1,38 @@
{
"name": "quixzoom-app",
"name": "quixzoom-mobile",
"version": "1.0.0",
"description": "quiXzoom Native App — Field data collection for iOS/Android",
"main": "index.js",
"main": "index.ts",
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"test": "jest",
"lint": "eslint ."
"start": "expo start",
"android": "expo start --android",
"ios": "expo start --ios",
"web": "expo start --web"
},
"dependencies": {
"react": "18.2.0",
"react-native": "0.72.0",
"@react-native-async-storage/async-storage": "^1.19.0",
"@react-native-community/geolocation": "^3.0.0",
"react-native-vision-camera": "^3.0.0",
"react-native-maps": "^1.7.0",
"react-native-vector-icons": "^10.0.0",
"react-native-share": "^9.0.0",
"react-native-fs": "^2.20.0",
"react-native-image-resizer": "^3.0.0",
"react-native-background-upload": "^6.0.0",
"react-native-offline": "^6.0.0",
"react-native-permissions": "^3.8.0",
"react-native-device-info": "^10.0.0",
"react-native-sensors": "^7.3.0",
"react-native-keep-awake": "^4.0.0",
"react-native-haptic-feedback": "^2.0.0",
"react-native-localize": "^3.0.0",
"react-native-keychain": "^8.1.0",
"react-native-encrypted-storage": "^4.0.0",
"@react-navigation/native": "^6.1.0",
"@react-navigation/bottom-tabs": "^6.5.0",
"@react-navigation/stack": "^6.3.0",
"react-native-gesture-handler": "^2.12.0",
"react-native-reanimated": "^3.3.0",
"react-native-screens": "^3.22.0",
"react-native-safe-area-context": "^4.6.0",
"@reduxjs/toolkit": "^1.9.0",
"react-redux": "^8.1.0",
"redux-persist": "^6.0.0",
"axios": "^1.4.0",
"date-fns": "^2.30.0",
"uuid": "^9.0.0"
"@react-navigation/bottom-tabs": "^7.15.7",
"@react-navigation/native": "^7.2.0",
"@react-navigation/native-stack": "^7.14.7",
"@supabase/supabase-js": "^2.100.0",
"@tanstack/react-query": "^5.95.2",
"expo": "~55.0.8",
"expo-camera": "~55.0.10",
"expo-image-picker": "~55.0.13",
"expo-linking": "~7.1.4",
"expo-location": "~55.1.4",
"expo-notifications": "^0.30.7",
"expo-secure-store": "~55.0.9",
"expo-status-bar": "~55.0.4",
"react": "19.2.0",
"react-native": "0.83.2",
"react-native-gesture-handler": "~2.30.0",
"react-native-maps": "^1.27.2",
"react-native-safe-area-context": "^5.7.0",
"react-native-screens": "~4.23.0",
"zustand": "^5.0.12"
},
"devDependencies": {
"@babel/core": "^7.20.0",
"@babel/preset-env": "^7.20.0",
"@babel/runtime": "^7.20.0",
"@react-native/eslint-config": "^0.72.0",
"@react-native/metro-config": "^0.72.0",
"@tsconfig/react-native": "^3.0.0",
"@types/react": "^18.0.24",
"@types/react-test-renderer": "^18.0.0",
"babel-jest": "^29.2.1",
"eslint": "^8.19.0",
"jest": "^29.2.1",
"metro-react-native-babel-preset": "0.76.5",
"prettier": "^2.4.1",
"react-test-renderer": "18.2.0",
"typescript": "4.8.4"
"@types/react": "~19.2.2",
"typescript": "~5.9.2"
},
"jest": {
"preset": "react-native"
}
"private": true
}
+263
View File
@@ -0,0 +1,263 @@
/**
* EarningsScreen — saldo + utbetalningshistorik
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, RefreshControl, Alert,
} from 'react-native'
import { wallet, payouts, type WalletBalance, type Payout } from '../lib/api'
type TabKey = 'oversikt' | 'utbetalningar'
const PAYOUT_STATUS: Record<string, { label: string; color: string }> = {
pending: { label: 'Väntar', color: '#f59e0b' },
approved: { label: 'Godkänd', color: '#10b981' },
paid: { label: 'Utbetald', color: '#6366f1' },
rejected: { label: 'Nekad', color: '#ef4444' },
}
export function EarningsScreen() {
const [tab, setTab] = useState<TabKey>('oversikt')
const [balance, setBalance] = useState<WalletBalance | null>(null)
const [payoutList, setPayoutList] = useState<Payout[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [requesting, setRequesting] = useState(false)
const load = useCallback(async () => {
try {
const [b, p] = await Promise.all([
wallet.balance(),
payouts.history(),
])
setBalance(b)
setPayoutList(p)
} catch {
// silent
} finally {
setLoading(false)
setRefreshing(false)
}
}, [])
useEffect(() => { load() }, [])
const onRefresh = useCallback(() => { setRefreshing(true); load() }, [load])
const handleRequestPayout = () => {
if (!balance) return
const available = balance.balance - balance.pending_payouts
if (available < 10000) { // 100 SEK minimum
Alert.alert('Minsta utbetalning 100 kr', `Du har ${(available / 100).toFixed(0)} kr tillgängligt.`)
return
}
Alert.alert(
'Begär utbetalning',
`Vill du ta ut ${(available / 100).toFixed(0)} SEK till ditt bankkonto?`,
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Ta ut',
onPress: async () => {
setRequesting(true)
try {
await payouts.request(available)
Alert.alert('✅ Utbetalning begärd!', 'Pengarna är hos dig inom 13 bankdagar.')
load()
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte begära utbetalning.')
} finally {
setRequesting(false)
}
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
return (
<ScrollView
style={styles.container}
contentContainerStyle={{ paddingBottom: 40 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />}
>
{/* Big balance card */}
<View style={styles.balanceCard}>
<Text style={styles.balanceLabel}>Tillgängligt</Text>
<Text style={styles.balanceAmount}>
{balance ? (balance.balance / 100).toFixed(0) : '0'} SEK
</Text>
{balance && balance.pending_payouts > 0 && (
<Text style={styles.pendingNote}>
+ {(balance.pending_payouts / 100).toFixed(0)} SEK under behandling
</Text>
)}
<TouchableOpacity
style={[styles.payoutBtn, requesting && styles.payoutBtnDisabled]}
onPress={handleRequestPayout}
disabled={requesting}
>
{requesting
? <ActivityIndicator color="#fff" size="small" />
: <Text style={styles.payoutBtnText}>💳 Begär utbetalning</Text>
}
</TouchableOpacity>
</View>
{/* Tab bar */}
<View style={styles.tabBar}>
{([['oversikt', 'Översikt'], ['utbetalningar', 'Utbetalningar']] as [TabKey, string][]).map(([key, label]) => (
<TouchableOpacity
key={key}
style={[styles.tab, tab === key && styles.tabActive]}
onPress={() => setTab(key)}
>
<Text style={[styles.tabText, tab === key && styles.tabTextActive]}>{label}</Text>
</TouchableOpacity>
))}
</View>
{/* Översikt */}
{tab === 'oversikt' && (
<View style={styles.section}>
<Text style={styles.sectionTitle}>Din ekonomi</Text>
<View style={styles.infoCard}>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Valuta</Text>
<Text style={styles.infoValue}>{balance?.currency ?? 'SEK'}</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Tillgängligt</Text>
<Text style={styles.infoValue}>{balance ? (balance.balance / 100).toFixed(0) : '0'} kr</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Väntande utbetalningar</Text>
<Text style={styles.infoValue}>{balance ? (balance.pending_payouts / 100).toFixed(0) : '0'} kr</Text>
</View>
<View style={styles.infoRow}>
<Text style={styles.infoLabel}>Totalt utbetalt</Text>
<Text style={styles.infoValue}>
{payoutList
.filter(p => p.status === 'paid')
.reduce((sum, p) => sum + p.amount, 0) / 100} kr
</Text>
</View>
</View>
</View>
)}
{/* Utbetalningar */}
{tab === 'utbetalningar' && (
<View style={styles.section}>
{payoutList.length === 0
? (
<View style={styles.emptyWrap}>
<Text style={styles.emptyEmoji}>💳</Text>
<Text style={styles.emptyTitle}>Inga utbetalningar ännu</Text>
<Text style={styles.empty}>Begär din första utbetalning ovan.</Text>
</View>
)
: payoutList.map(p => {
const s = PAYOUT_STATUS[p.status] ?? { label: p.status, color: '#fff' }
return (
<View key={p.id} style={styles.payoutRow}>
<View>
<Text style={styles.payoutDate}>
{new Date(p.created_at).toLocaleDateString('sv-SE', { month: 'short', day: 'numeric', year: 'numeric' })}
</Text>
{p.paid_at && (
<Text style={styles.payoutPaidAt}>
Betald {new Date(p.paid_at).toLocaleDateString('sv-SE')}
</Text>
)}
</View>
<Text style={styles.payoutAmount}>
{(p.amount / 100).toFixed(0)} {p.currency}
</Text>
<View style={[styles.payoutStatusBadge, { backgroundColor: s.color + '22' }]}>
<Text style={[styles.payoutStatusText, { color: s.color }]}>{s.label}</Text>
</View>
</View>
)
})
}
</View>
)}
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
balanceCard: {
margin: 16,
marginTop: 56,
backgroundColor: '#6366f1',
borderRadius: 24, padding: 28,
alignItems: 'center',
},
balanceLabel: { color: 'rgba(255,255,255,0.7)', fontSize: 14, marginBottom: 6 },
balanceAmount: { color: '#fff', fontSize: 48, fontWeight: '900', letterSpacing: -2 },
pendingNote: { color: 'rgba(255,255,255,0.6)', fontSize: 13, marginTop: 4 },
payoutBtn: {
marginTop: 20,
backgroundColor: 'rgba(255,255,255,0.15)',
borderRadius: 14, paddingHorizontal: 28, paddingVertical: 12,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.25)',
},
payoutBtnDisabled: { opacity: 0.6 },
payoutBtnText: { color: '#fff', fontWeight: '700', fontSize: 15 },
tabBar: {
flexDirection: 'row', marginHorizontal: 16, marginTop: 16, marginBottom: 4,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 4,
},
tab: { flex: 1, paddingVertical: 8, alignItems: 'center', borderRadius: 10 },
tabActive: { backgroundColor: '#6366f1' },
tabText: { color: 'rgba(255,255,255,0.4)', fontSize: 12, fontWeight: '600' },
tabTextActive: { color: '#fff' },
section: { paddingHorizontal: 16, paddingTop: 16 },
sectionTitle: { color: 'rgba(255,255,255,0.35)', fontSize: 12, fontWeight: '700', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 16 },
infoCard: {
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 16, padding: 20,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
infoRow: {
flexDirection: 'row', justifyContent: 'space-between',
paddingVertical: 12,
borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)',
},
infoLabel: { color: 'rgba(255,255,255,0.5)', fontSize: 14 },
infoValue: { color: '#fff', fontSize: 14, fontWeight: '700' },
emptyWrap: { alignItems: 'center', paddingTop: 40 },
emptyEmoji: { fontSize: 40, marginBottom: 12 },
emptyTitle: { color: '#fff', fontSize: 16, fontWeight: '700', marginBottom: 6 },
empty: { color: 'rgba(255,255,255,0.3)', textAlign: 'center', fontSize: 14 },
payoutRow: {
flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between',
paddingVertical: 14,
borderBottomWidth: 1, borderBottomColor: 'rgba(255,255,255,0.05)',
},
payoutDate: { color: '#fff', fontSize: 13, fontWeight: '500' },
payoutPaidAt: { color: 'rgba(255,255,255,0.3)', fontSize: 11 },
payoutAmount: { color: '#fff', fontWeight: '700', fontSize: 14 },
payoutStatusBadge: { borderRadius: 20, paddingHorizontal: 10, paddingVertical: 4 },
payoutStatusText: { fontSize: 12, fontWeight: '600' },
})
+332
View File
@@ -0,0 +1,332 @@
/**
* HomeScreen — karta med live-uppdrag + snabbstatistik (intjänat idag)
* Integrerad med useLiveLocation() för kontinuerlig GPS-tracking
*/
import { useCallback, useEffect, useRef, useState } from 'react'
import {
View, Text, StyleSheet, TouchableOpacity,
ActivityIndicator, Platform,
} from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import { useNavigation } from '@react-navigation/native'
import { missions, wallet, type Mission, type WalletBalance } from '../lib/api'
import { useLiveLocation } from '../src/hooks/useLiveLocation'
import { useHeading } from '../src/hooks/useHeading'
import { useLocationStore } from '../src/stores/locationStore'
import { UserLocationMarker } from '../src/components/gps/UserLocationMarker'
import { FollowUserButton } from '../src/components/gps/FollowUserButton'
import { GpsDiagnostics } from '../src/components/gps/GpsDiagnostics'
const STOCKHOLM_ARCHIPELAGO: Region = {
latitude: 59.45,
longitude: 18.5,
latitudeDelta: 0.8,
longitudeDelta: 0.8,
}
const CATEGORY_EMOJI: Record<string, string> = {
infrastruktur: '🏗️',
vägar: '🛤️',
hamnar: '⚓',
bryggor: '🚢',
fritidshus: '🏠',
miljö: '🌿',
övrigt: '📍',
}
export function HomeScreen() {
const navigation = useNavigation<any>()
const mapRef = useRef<MapView>(null)
// Initialize live location tracking
useLiveLocation()
useHeading()
const {
currentPosition,
followUser,
setUserPannedMap,
locationAuthStatus,
} = useLocationStore()
const [region, setRegion] = useState<Region>(STOCKHOLM_ARCHIPELAGO)
const [nearbyMissions, setNearbyMissions] = useState<Mission[]>([])
const [balance, setBalance] = useState<WalletBalance | null>(null)
const [loadingMissions, setLoadingMissions] = useState(false)
const [showDiagnostics, setShowDiagnostics] = useState(false)
const [initialLocationSet, setInitialLocationSet] = useState(false)
// Center map on first position fix
useEffect(() => {
if (currentPosition && !initialLocationSet) {
const newRegion: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.5,
longitudeDelta: 0.5,
}
setRegion(newRegion)
mapRef.current?.animateToRegion(newRegion, 800)
fetchNearby(currentPosition.coords.latitude, currentPosition.coords.longitude)
setInitialLocationSet(true)
}
}, [currentPosition, initialLocationSet])
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const newRegion: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: region.latitudeDelta,
longitudeDelta: region.longitudeDelta,
}
mapRef.current.animateToRegion(newRegion, 300)
}
}, [currentPosition, followUser, region.latitudeDelta, region.longitudeDelta])
// Wallet balance
useEffect(() => {
wallet.balance().then(setBalance).catch(() => {})
}, [])
const fetchNearby = useCallback(async (lat: number, lng: number) => {
setLoadingMissions(true)
try {
const data = await missions.nearby(lat, lng, 60)
setNearbyMissions(data)
} catch {
// silent
} finally {
setLoadingMissions(false)
}
}, [])
const onRegionChangeComplete = useCallback((r: Region) => {
setRegion(r)
fetchNearby(r.latitude, r.longitude)
}, [fetchNearby])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
const availableSEK = balance ? ((balance.balance - balance.pending_payouts) / 100).toFixed(0) : '—'
const pendingSEK = balance ? (balance.pending_payouts / 100).toFixed(0) : '—'
return (
<View style={styles.container}>
{/* Map */}
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
initialRegion={STOCKHOLM_ARCHIPELAGO}
region={region}
onRegionChangeComplete={onRegionChangeComplete}
onPanDrag={onPanDrag}
showsUserLocation={false} // We use custom UserLocationMarker
showsMyLocationButton={false}
rotateEnabled={false} // Map stays north-up, marker rotates
>
{/* Custom user location marker with heading */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
{nearbyMissions.map(m => (
<Marker
key={m.id}
coordinate={{ latitude: m.latitude, longitude: m.longitude }}
title={m.title}
description={`${(m.reward_amount / 100).toFixed(0)} SEK`}
onCalloutPress={() => navigation.navigate('MissionDetail', { missionId: m.id })}
>
<View style={styles.markerBubble}>
<Text style={styles.markerEmoji}>
{CATEGORY_EMOJI[m.category] ?? '📍'}
</Text>
<Text style={styles.markerReward}>
{(m.reward_amount / 100).toFixed(0)} kr
</Text>
</View>
</Marker>
))}
</MapView>
{/* Loading indicator */}
{loadingMissions && (
<View style={styles.loadingOverlay}>
<ActivityIndicator color="#6366f1" size="small" />
</View>
)}
{/* GPS Diagnostics toggle */}
<TouchableOpacity
style={styles.diagnosticsToggle}
onPress={() => setShowDiagnostics(prev => !prev)}
>
<Text style={styles.diagnosticsToggleText}>
{showDiagnostics ? '🔧' : '🛠️'}
</Text>
</TouchableOpacity>
{/* GPS Diagnostics overlay */}
{showDiagnostics && <GpsDiagnostics />}
{/* Stats card */}
<View style={styles.statsCard}>
<View style={styles.statItem}>
<Text style={styles.statLabel}>Tillgängligt</Text>
<Text style={styles.statValue}>{availableSEK} kr</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statLabel}>Under behandling</Text>
<Text style={[styles.statValue, { color: '#f59e0b' }]}>{pendingSEK} kr</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statLabel}>Uppdrag nära</Text>
<Text style={[styles.statValue, { color: '#10b981' }]}>{nearbyMissions.length}</Text>
</View>
</View>
{/* Follow user button */}
<View style={styles.followBtnContainer}>
<FollowUserButton />
</View>
{/* Location status indicator */}
{locationAuthStatus !== 'granted' && locationAuthStatus !== null && (
<View style={styles.locationWarning}>
<Text style={styles.locationWarningText}>
GPS-behörighet: {locationAuthStatus}
</Text>
</View>
)}
{/* Missions list button */}
<TouchableOpacity
style={styles.missionsBtn}
onPress={() => navigation.navigate('Uppdrag')}
>
<Text style={styles.missionsBtnText}>
🎯 {nearbyMissions.length} uppdrag nära dig
</Text>
</TouchableOpacity>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
map: { flex: 1 },
markerBubble: {
backgroundColor: '#6366f1',
borderRadius: 20,
paddingHorizontal: 10,
paddingVertical: 6,
alignItems: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.4,
shadowRadius: 4,
elevation: 4,
},
markerEmoji: { fontSize: 14 },
markerReward: { color: '#fff', fontSize: 11, fontWeight: '700' },
loadingOverlay: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
right: 16,
backgroundColor: 'rgba(0,0,0,0.7)',
borderRadius: 20,
padding: 8,
},
diagnosticsToggle: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
right: 60,
width: 36,
height: 36,
backgroundColor: 'rgba(10,10,27,0.9)',
borderRadius: 18,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.15)',
alignItems: 'center',
justifyContent: 'center',
},
diagnosticsToggleText: { fontSize: 16 },
statsCard: {
position: 'absolute',
top: Platform.OS === 'ios' ? 60 : 16,
left: 16,
right: 104,
flexDirection: 'row',
backgroundColor: 'rgba(10,10,27,0.92)',
borderRadius: 16,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
padding: 12,
alignItems: 'center',
},
statItem: { flex: 1, alignItems: 'center' },
statLabel: { color: 'rgba(255,255,255,0.45)', fontSize: 10, marginBottom: 2 },
statValue: { color: '#fff', fontSize: 15, fontWeight: '800' },
statDivider: { width: 1, height: 28, backgroundColor: 'rgba(255,255,255,0.1)' },
followBtnContainer: {
position: 'absolute',
bottom: 100,
right: 16,
},
locationWarning: {
position: 'absolute',
top: Platform.OS === 'ios' ? 120 : 76,
left: 16,
right: 16,
backgroundColor: 'rgba(245,158,11,0.2)',
borderRadius: 12,
padding: 10,
borderWidth: 1,
borderColor: 'rgba(245,158,11,0.4)',
alignItems: 'center',
},
locationWarningText: {
color: '#f59e0b',
fontSize: 12,
fontWeight: '600',
},
missionsBtn: {
position: 'absolute',
bottom: 32,
left: 16,
right: 16,
backgroundColor: '#6366f1',
borderRadius: 16,
paddingVertical: 16,
alignItems: 'center',
shadowColor: '#6366f1',
shadowOffset: { width: 0, height: 4 },
shadowOpacity: 0.4,
shadowRadius: 12,
elevation: 8,
},
missionsBtnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
})
@@ -0,0 +1,593 @@
/**
* MissionDetailScreen — uppdragsinfo, karta, acceptera-knapp, kamera-upload vid leverans
* Live markör, distance overlay, heading indicator
*/
import { useEffect, useState, useRef, useCallback } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, Alert,
Image, Platform,
} from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import * as ImagePicker from 'expo-image-picker'
import { useNavigation, useRoute, type RouteProp } from '@react-navigation/native'
import { missions, type MissionDetail } from '../lib/api'
import { useLocationStore } from '../src/stores/locationStore'
import { UserLocationMarker } from '../src/components/gps/UserLocationMarker'
import { FollowUserButton } from '../src/components/gps/FollowUserButton'
type RouteParams = { MissionDetail: { missionId: string } }
const CATEGORY_EMOJI: Record<string, string> = {
infrastruktur: '🏗️',
vägar: '🛤️',
hamnar: '⚓',
bryggor: '🚢',
fritidshus: '🏠',
miljö: '🌿',
övrigt: '📍',
}
/** Calculate distance between two coordinates in meters */
function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371e3 // Earth radius in meters
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δφ = ((lat2 - lat1) * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
function formatDistance(meters: number): string {
if (meters < 1000) {
return `${Math.round(meters)} m`
}
return `${(meters / 1000).toFixed(1)} km`
}
/** Calculate bearing from point 1 to point 2 in degrees */
function calculateBearing(lat1: number, lng1: number, lat2: number, lng2: number): number {
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const y = Math.sin(Δλ) * Math.cos(φ2)
const x =
Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ)
const θ = Math.atan2(y, x)
return ((θ * 180) / Math.PI + 360) % 360
}
function getDirectionArrow(degrees: number): string {
if (degrees >= 337.5 || degrees < 22.5) return '↑'
if (degrees >= 22.5 && degrees < 67.5) return '↗'
if (degrees >= 67.5 && degrees < 112.5) return '→'
if (degrees >= 112.5 && degrees < 157.5) return '↘'
if (degrees >= 157.5 && degrees < 202.5) return '↓'
if (degrees >= 202.5 && degrees < 247.5) return '↙'
if (degrees >= 247.5 && degrees < 292.5) return '←'
return '↖'
}
export function MissionDetailScreen() {
const navigation = useNavigation<any>()
const route = useRoute<RouteProp<RouteParams, 'MissionDetail'>>()
const { missionId } = route.params
const mapRef = useRef<MapView>(null)
// Live location from store
const {
currentPosition,
heading,
followUser,
setUserPannedMap,
} = useLocationStore()
const [mission, setMission] = useState<MissionDetail | null>(null)
const [loading, setLoading] = useState(true)
const [accepting, setAccepting] = useState(false)
const [submitting, setSubmitting] = useState(false)
const [accepted, setAccepted] = useState(false)
const [mediaUris, setMediaUris] = useState<string[]>([])
const [distance, setDistance] = useState<number | null>(null)
const [bearing, setBearing] = useState<number | null>(null)
useEffect(() => {
missions.get(missionId)
.then(m => { setMission(m); setLoading(false) })
.catch(() => { Alert.alert('Fel', 'Kunde inte hämta uppdraget.'); navigation.goBack() })
}, [missionId])
// Update distance and bearing when position or mission changes
useEffect(() => {
if (currentPosition && mission) {
const dist = haversineDistance(
currentPosition.coords.latitude,
currentPosition.coords.longitude,
mission.latitude,
mission.longitude
)
setDistance(dist)
const bear = calculateBearing(
currentPosition.coords.latitude,
currentPosition.coords.longitude,
mission.latitude,
mission.longitude
)
setBearing(bear)
}
}, [currentPosition, mission])
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const region: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}
mapRef.current.animateToRegion(region, 300)
}
}, [currentPosition, followUser])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
const handleAccept = async () => {
if (!mission) return
setAccepting(true)
try {
await missions.accept(mission.id)
setAccepted(true)
Alert.alert('✅ Uppdraget accepterat!', 'Ta dig till platsen och leverera när du är framme.')
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte acceptera uppdraget.')
} finally {
setAccepting(false)
}
}
const handlePickMedia = async () => {
const { status } = await ImagePicker.requestCameraPermissionsAsync()
if (status !== 'granted') {
Alert.alert('Kamera-åtkomst krävs', 'Tillåt kameraåtkomst i telefonens inställningar.')
return
}
Alert.alert('Välj källa', '', [
{
text: 'Ta foto',
onPress: async () => {
const result = await ImagePicker.launchCameraAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.85,
allowsEditing: false,
})
if (!result.canceled && result.assets[0]) {
setMediaUris(prev => [...prev, result.assets[0].uri])
}
},
},
{
text: 'Välj från bibliotek',
onPress: async () => {
const result = await ImagePicker.launchImageLibraryAsync({
mediaTypes: ImagePicker.MediaTypeOptions.Images,
quality: 0.85,
allowsMultipleSelection: true,
selectionLimit: 10,
})
if (!result.canceled) {
setMediaUris(prev => [...prev, ...result.assets.map(a => a.uri)])
}
},
},
{ text: 'Avbryt', style: 'cancel' },
])
}
const handleSubmit = async () => {
if (!mission) return
if (mediaUris.length === 0) {
Alert.alert('Inga bilder', 'Lägg till minst en bild innan du skickar in.')
return
}
Alert.alert(
'Skicka in uppdraget?',
`${mediaUris.length} bild${mediaUris.length > 1 ? 'er' : ''} kommer att skickas.`,
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Skicka in',
onPress: async () => {
setSubmitting(true)
try {
await missions.submit(mission.id, mediaUris)
Alert.alert(
'🎉 Inlämnat!',
`Dina bilder granskas. Om allt godkänns får du ${(mission.reward_amount / 100).toFixed(0)} kr.`,
[{ text: 'Tillbaka', onPress: () => navigation.goBack() }],
)
} catch (e: any) {
Alert.alert('Fel', e?.message ?? 'Kunde inte skicka in uppdraget.')
} finally {
setSubmitting(false)
}
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
if (!mission) return null
const isExpired = mission.deadline_at ? new Date(mission.deadline_at) < new Date() : false
const spotsLeft = mission.max_submissions - mission.submission_count
// Calculate relative bearing for navigation arrow
const relativeBearing = bearing != null && heading != null
? ((bearing - heading + 360) % 360)
: null
return (
<View style={styles.container}>
<ScrollView showsVerticalScrollIndicator={false} contentContainerStyle={{ paddingBottom: 120 }}>
{/* Header */}
<View style={styles.header}>
<TouchableOpacity style={styles.backBtn} onPress={() => navigation.goBack()}>
<Text style={styles.backBtnText}> Tillbaka</Text>
</TouchableOpacity>
</View>
{/* Map with live tracking */}
<View style={styles.mapContainer}>
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
region={{
latitude: mission.latitude,
longitude: mission.longitude,
latitudeDelta: 0.05,
longitudeDelta: 0.05,
}}
onPanDrag={onPanDrag}
showsUserLocation={false} // Custom marker
rotateEnabled={false}
>
{/* Mission marker */}
<Marker
coordinate={{ latitude: mission.latitude, longitude: mission.longitude }}
pinColor="#10b981"
title={mission.title}
/>
{/* Live user location marker */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
</MapView>
{/* Follow user button */}
<View style={styles.followBtnContainer}>
<FollowUserButton />
</View>
{/* Distance overlay */}
{distance != null && (
<View style={styles.distanceOverlay}>
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Avstånd till uppdrag</Text>
<Text style={styles.distanceValue}>{formatDistance(distance)}</Text>
</View>
{relativeBearing != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Riktning</Text>
<Text style={styles.distanceValue}>
{getDirectionArrow(relativeBearing)} {relativeBearing.toFixed(0)}°
</Text>
</View>
)}
{heading != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Din kurs</Text>
<Text style={styles.distanceValue}>{heading.toFixed(0)}°</Text>
</View>
)}
{currentPosition?.coords.speed != null && (
<View style={styles.distanceRow}>
<Text style={styles.distanceLabel}>Hastighet</Text>
<Text style={styles.distanceValue}>
{(currentPosition.coords.speed * 3.6).toFixed(1)} km/h
</Text>
</View>
)}
</View>
)}
</View>
{/* Content */}
<View style={styles.content}>
{/* Category + status row */}
<View style={styles.metaRow}>
<View style={styles.categoryBadge}>
<Text style={styles.categoryEmoji}>{CATEGORY_EMOJI[mission.category] ?? '📍'}</Text>
<Text style={styles.categoryText}>{mission.category}</Text>
</View>
{isExpired && <View style={styles.expiredBadge}><Text style={styles.expiredText}>Utgången</Text></View>}
{spotsLeft <= 3 && !isExpired && (
<View style={styles.urgentBadge}>
<Text style={styles.urgentText}>Bara {spotsLeft} platser kvar</Text>
</View>
)}
</View>
{/* Title */}
<Text style={styles.title}>{mission.title}</Text>
{/* Reward + deadline */}
<View style={styles.rewardRow}>
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Ersättning</Text>
<Text style={styles.rewardValue}>{(mission.reward_amount / 100).toFixed(0)} SEK</Text>
</View>
{mission.deadline_at && (
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Deadline</Text>
<Text style={styles.rewardValue}>
{new Date(mission.deadline_at).toLocaleDateString('sv-SE', {
month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit',
})}
</Text>
</View>
)}
<View style={styles.rewardBox}>
<Text style={styles.rewardLabel}>Plats</Text>
<Text style={styles.rewardValue}>{mission.city || 'Skärgården'}</Text>
</View>
</View>
{/* Description */}
<Text style={styles.sectionTitle}>Beskrivning</Text>
<Text style={styles.desc}>{mission.description}</Text>
{/* Instructions */}
{mission.instructions && (
<>
<Text style={styles.sectionTitle}>Instruktioner</Text>
<Text style={styles.desc}>{mission.instructions}</Text>
</>
)}
{/* Requirements */}
{mission.requirements?.length > 0 && (
<>
<Text style={styles.sectionTitle}>Krav</Text>
{mission.requirements.map((r, i) => (
<View key={i} style={styles.reqRow}>
<Text style={styles.reqBullet}></Text>
<Text style={styles.reqText}>{r}</Text>
</View>
))}
</>
)}
{/* Media picker (only when accepted) */}
{accepted && (
<>
<Text style={styles.sectionTitle}>Dina bilder</Text>
<View style={styles.mediaGrid}>
{mediaUris.map((uri, i) => (
<View key={i} style={styles.mediaThumbWrap}>
<Image source={{ uri }} style={styles.mediaThumb} />
<TouchableOpacity
style={styles.removeBtn}
onPress={() => setMediaUris(prev => prev.filter((_, j) => j !== i))}
>
<Text style={styles.removeBtnText}></Text>
</TouchableOpacity>
</View>
))}
<TouchableOpacity style={styles.addMediaBtn} onPress={handlePickMedia}>
<Text style={styles.addMediaIcon}>📸</Text>
<Text style={styles.addMediaText}>Lägg till bild</Text>
</TouchableOpacity>
</View>
</>
)}
</View>
</ScrollView>
{/* Bottom action bar */}
{!isExpired && (
<View style={styles.bottomBar}>
{!accepted ? (
<TouchableOpacity
style={[styles.actionBtn, accepting && styles.actionBtnDisabled]}
onPress={handleAccept}
disabled={accepting}
>
{accepting
? <ActivityIndicator color="#fff" />
: <Text style={styles.actionBtnText}>🎯 Acceptera uppdraget</Text>
}
</TouchableOpacity>
) : (
<TouchableOpacity
style={[styles.actionBtn, styles.submitBtn, (submitting || mediaUris.length === 0) && styles.actionBtnDisabled]}
onPress={handleSubmit}
disabled={submitting || mediaUris.length === 0}
>
{submitting
? <ActivityIndicator color="#fff" />
: <Text style={styles.actionBtnText}>
📤 Skicka in ({mediaUris.length} bild{mediaUris.length !== 1 ? 'er' : ''})
</Text>
}
</TouchableOpacity>
)}
</View>
)}
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
header: {
paddingTop: Platform.OS === 'ios' ? 56 : 16,
paddingHorizontal: 16,
paddingBottom: 8,
},
backBtn: { paddingVertical: 4 },
backBtnText: { color: '#6366f1', fontSize: 15, fontWeight: '600' },
mapContainer: {
height: 280,
position: 'relative',
},
map: {
flex: 1,
},
followBtnContainer: {
position: 'absolute',
bottom: 16,
right: 16,
},
distanceOverlay: {
position: 'absolute',
top: 16,
left: 16,
backgroundColor: 'rgba(10,10,27,0.85)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 160,
},
distanceRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
distanceLabel: {
color: 'rgba(255,255,255,0.45)',
fontSize: 11,
marginRight: 12,
},
distanceValue: {
color: '#fff',
fontSize: 13,
fontWeight: '700',
},
content: { padding: 20 },
metaRow: { flexDirection: 'row', alignItems: 'center', gap: 10, marginBottom: 12 },
categoryBadge: {
flexDirection: 'row', alignItems: 'center', gap: 6,
backgroundColor: 'rgba(99,102,241,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
categoryEmoji: { fontSize: 14 },
categoryText: { color: '#a5b4fc', fontSize: 12, fontWeight: '600', textTransform: 'capitalize' },
expiredBadge: {
backgroundColor: 'rgba(239,68,68,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
expiredText: { color: '#ef4444', fontSize: 12, fontWeight: '600' },
urgentBadge: {
backgroundColor: 'rgba(245,158,11,0.15)',
borderRadius: 20, paddingHorizontal: 12, paddingVertical: 6,
},
urgentText: { color: '#f59e0b', fontSize: 12, fontWeight: '600' },
title: { color: '#fff', fontSize: 24, fontWeight: '800', lineHeight: 30, marginBottom: 20, letterSpacing: -0.5 },
rewardRow: { flexDirection: 'row', gap: 12, marginBottom: 24 },
rewardBox: {
flex: 1,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 14,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
rewardLabel: { color: 'rgba(255,255,255,0.4)', fontSize: 11, marginBottom: 4 },
rewardValue: { color: '#fff', fontSize: 15, fontWeight: '700' },
sectionTitle: { color: 'rgba(255,255,255,0.5)', fontSize: 12, fontWeight: '700', letterSpacing: 1, textTransform: 'uppercase', marginBottom: 8, marginTop: 4 },
desc: { color: 'rgba(255,255,255,0.7)', fontSize: 15, lineHeight: 24, marginBottom: 20 },
reqRow: { flexDirection: 'row', gap: 10, marginBottom: 8 },
reqBullet: { color: '#10b981', fontWeight: '700', fontSize: 14 },
reqText: { color: 'rgba(255,255,255,0.65)', fontSize: 14, lineHeight: 20, flex: 1 },
mediaGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10, marginBottom: 8 },
mediaThumbWrap: { position: 'relative' },
mediaThumb: { width: 90, height: 90, borderRadius: 12 },
removeBtn: {
position: 'absolute', top: -6, right: -6,
width: 22, height: 22, borderRadius: 11,
backgroundColor: '#ef4444', alignItems: 'center', justifyContent: 'center',
},
removeBtnText: { color: '#fff', fontSize: 10, fontWeight: '800' },
addMediaBtn: {
width: 90, height: 90, borderRadius: 12,
backgroundColor: 'rgba(255,255,255,0.04)',
borderWidth: 2, borderColor: 'rgba(255,255,255,0.12)',
borderStyle: 'dashed',
alignItems: 'center', justifyContent: 'center', gap: 4,
},
addMediaIcon: { fontSize: 24 },
addMediaText: { color: 'rgba(255,255,255,0.4)', fontSize: 11 },
bottomBar: {
position: 'absolute', bottom: 0, left: 0, right: 0,
padding: 16,
paddingBottom: Platform.OS === 'ios' ? 32 : 16,
backgroundColor: 'rgba(10,10,27,0.95)',
borderTopWidth: 1, borderTopColor: 'rgba(255,255,255,0.08)',
},
actionBtn: {
backgroundColor: '#6366f1',
borderRadius: 16, paddingVertical: 16,
alignItems: 'center', justifyContent: 'center',
},
submitBtn: { backgroundColor: '#10b981' },
actionBtnDisabled: { opacity: 0.5 },
actionBtnText: { color: '#fff', fontWeight: '700', fontSize: 16 },
})
+338
View File
@@ -0,0 +1,338 @@
/**
* MissionsScreen — lista aktiva uppdrag nära mig, filter, sortering
* Läser position från locationStore för live-avståndsuppdateringar
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, FlatList, TouchableOpacity,
StyleSheet, RefreshControl, ActivityIndicator,
ScrollView,
} from 'react-native'
import { useNavigation } from '@react-navigation/native'
import { missions, type Mission, type MissionCategory } from '../lib/api'
import { useLocationStore } from '../src/stores/locationStore'
type SortKey = 'distance' | 'reward' | 'deadline'
const CATEGORIES: { key: MissionCategory | 'alla'; label: string; emoji: string }[] = [
{ key: 'alla', label: 'Alla', emoji: '🗺️' },
{ key: 'hamnar', label: 'Hamnar', emoji: '⚓' },
{ key: 'bryggor', label: 'Bryggor', emoji: '🚢' },
{ key: 'vägar', label: 'Vägar', emoji: '🛤️' },
{ key: 'infrastruktur', label: 'Infrastruktur', emoji: '🏗️' },
{ key: 'fritidshus', label: 'Fritidshus', emoji: '🏠' },
{ key: 'miljö', label: 'Miljö', emoji: '🌿' },
]
const STATUS_COLOR: Record<string, string> = {
open: '#10b981',
active: '#f59e0b',
completed: '#6366f1',
cancelled: '#ef4444',
expired: '#6b7280',
}
function timeLeft(deadline: string | null): string {
if (!deadline) return ''
const diff = new Date(deadline).getTime() - Date.now()
if (diff < 0) return 'Utgången'
const h = Math.floor(diff / 3600000)
if (h < 24) return `${h}h kvar`
return `${Math.floor(h / 24)}d kvar`
}
/** Calculate distance between two coordinates in meters */
function haversineDistance(lat1: number, lng1: number, lat2: number, lng2: number): number {
const R = 6371e3 // Earth radius in meters
const φ1 = (lat1 * Math.PI) / 180
const φ2 = (lat2 * Math.PI) / 180
const Δφ = ((lat2 - lat1) * Math.PI) / 180
const Δλ = ((lng2 - lng1) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
return R * c
}
function formatDistance(meters: number): string {
if (meters < 1000) {
return `${Math.round(meters)} m`
}
return `${(meters / 1000).toFixed(1)} km`
}
export function MissionsScreen() {
const navigation = useNavigation<any>()
// Read live position from store
const { currentPosition } = useLocationStore()
const [allMissions, setAllMissions] = useState<Mission[]>([])
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const [category, setCategory] = useState<MissionCategory | 'alla'>('alla')
const [sort, setSort] = useState<SortKey>('distance')
const [userLat, setUserLat] = useState<number | null>(null)
const [userLng, setUserLng] = useState<number | null>(null)
// Initialize with store position or fallback
useEffect(() => {
if (currentPosition) {
setUserLat(currentPosition.coords.latitude)
setUserLng(currentPosition.coords.longitude)
loadMissions(currentPosition.coords.latitude, currentPosition.coords.longitude)
} else {
// Fallback to Stockholm archipelago if no GPS yet
setUserLat(59.45)
setUserLng(18.5)
loadMissions(59.45, 18.5)
}
}, []) // Only on mount — subsequent updates come from currentPosition
// Update user position when store updates
useEffect(() => {
if (currentPosition) {
setUserLat(currentPosition.coords.latitude)
setUserLng(currentPosition.coords.longitude)
}
}, [currentPosition])
const loadMissions = async (lat: number, lng: number) => {
try {
const data = await missions.nearby(lat, lng, 80)
setAllMissions(data)
} catch {
// silent — show empty
} finally {
setLoading(false)
setRefreshing(false)
}
}
const onRefresh = useCallback(() => {
setRefreshing(true)
const lat = userLat ?? 59.45
const lng = userLng ?? 18.5
loadMissions(lat, lng)
}, [userLat, userLng])
// Compute live distances using current position
const missionsWithLiveDistance = allMissions.map(m => {
if (userLat != null && userLng != null) {
const liveDistance = haversineDistance(userLat, userLng, m.latitude, m.longitude)
return { ...m, distance_meters: liveDistance }
}
return m
})
const filtered = missionsWithLiveDistance
.filter(m => category === 'alla' || m.category === category)
.sort((a, b) => {
if (sort === 'reward') return b.reward_amount - a.reward_amount
if (sort === 'deadline') {
if (!a.deadline_at) return 1
if (!b.deadline_at) return -1
return new Date(a.deadline_at).getTime() - new Date(b.deadline_at).getTime()
}
// distance — use live-calculated distance
return (a.distance_meters ?? Infinity) - (b.distance_meters ?? Infinity)
})
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
<Text style={styles.loadingText}>Hämtar uppdrag nära dig</Text>
</View>
)
}
return (
<View style={styles.container}>
<Text style={styles.heading}>Uppdrag</Text>
{/* Live position indicator */}
{currentPosition && (
<View style={styles.locationBar}>
<Text style={styles.locationText}>
📍 {currentPosition.coords.latitude.toFixed(4)}, {currentPosition.coords.longitude.toFixed(4)}
{currentPosition.coords.accuracy && `${Math.round(currentPosition.coords.accuracy)}m)`}
</Text>
</View>
)}
{/* Category filter */}
<ScrollView
horizontal
showsHorizontalScrollIndicator={false}
contentContainerStyle={styles.filterRow}
>
{CATEGORIES.map(c => (
<TouchableOpacity
key={c.key}
style={[styles.chip, category === c.key && styles.chipActive]}
onPress={() => setCategory(c.key)}
>
<Text style={styles.chipEmoji}>{c.emoji}</Text>
<Text style={[styles.chipText, category === c.key && styles.chipTextActive]}>
{c.label}
</Text>
</TouchableOpacity>
))}
</ScrollView>
{/* Sort bar */}
<View style={styles.sortBar}>
<Text style={styles.sortLabel}>Sortera:</Text>
{(['distance', 'reward', 'deadline'] as SortKey[]).map(s => (
<TouchableOpacity
key={s}
style={[styles.sortBtn, sort === s && styles.sortBtnActive]}
onPress={() => setSort(s)}
>
<Text style={[styles.sortBtnText, sort === s && styles.sortBtnTextActive]}>
{s === 'distance' ? 'Avstånd' : s === 'reward' ? 'Ersättning' : 'Deadline'}
</Text>
</TouchableOpacity>
))}
</View>
{/* Count */}
<Text style={styles.count}>
{filtered.length} uppdrag {category !== 'alla' ? `i "${category}"` : 'totalt'}
</Text>
<FlatList
data={filtered}
keyExtractor={m => m.id}
contentContainerStyle={{ paddingBottom: 32 }}
refreshControl={
<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />
}
renderItem={({ item: m }) => (
<TouchableOpacity
style={styles.card}
onPress={() => navigation.navigate('MissionDetail', { missionId: m.id })}
activeOpacity={0.75}
>
<View style={styles.cardTop}>
<View style={styles.cardMeta}>
<View style={[styles.statusDot, { backgroundColor: STATUS_COLOR[m.status] ?? '#6b7280' }]} />
<Text style={styles.category}>{m.category}</Text>
</View>
<Text style={styles.reward}>
{(m.reward_amount / 100).toFixed(0)} SEK
</Text>
</View>
<Text style={styles.title} numberOfLines={2}>{m.title}</Text>
<Text style={styles.desc} numberOfLines={2}>{m.description}</Text>
<View style={styles.cardFooter}>
<Text style={styles.location}>
📍 {m.city || 'Stockholms skärgård'}
{m.distance_meters != null
? ` · ${formatDistance(m.distance_meters)}`
: ''}
</Text>
{m.deadline_at && (
<Text style={[
styles.deadline,
timeLeft(m.deadline_at) === 'Utgången' && { color: '#ef4444' },
]}>
{timeLeft(m.deadline_at)}
</Text>
)}
</View>
</TouchableOpacity>
)}
ListEmptyComponent={
<View style={styles.emptyWrap}>
<Text style={styles.emptyEmoji}>🎯</Text>
<Text style={styles.emptyTitle}>Inga uppdrag just nu</Text>
<Text style={styles.emptyText}>Prova en annan kategori eller dra för att uppdatera.</Text>
</View>
}
/>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B', paddingTop: 56 },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center', gap: 12 },
loadingText: { color: 'rgba(255,255,255,0.4)', fontSize: 14 },
heading: {
color: '#fff', fontSize: 28, fontWeight: '800',
marginHorizontal: 16, marginBottom: 8, letterSpacing: -0.5,
},
locationBar: {
marginHorizontal: 16,
marginBottom: 8,
backgroundColor: 'rgba(99,102,241,0.1)',
borderRadius: 8,
paddingHorizontal: 10,
paddingVertical: 6,
borderWidth: 1,
borderColor: 'rgba(99,102,241,0.2)',
},
locationText: {
color: '#a5b4fc',
fontSize: 11,
fontWeight: '500',
},
filterRow: { paddingHorizontal: 16, gap: 8, paddingBottom: 4 },
chip: {
flexDirection: 'row', alignItems: 'center', gap: 6,
backgroundColor: 'rgba(255,255,255,0.05)',
borderWidth: 1, borderColor: 'rgba(255,255,255,0.08)',
borderRadius: 20, paddingHorizontal: 14, paddingVertical: 8,
},
chipActive: { backgroundColor: 'rgba(99,102,241,0.2)', borderColor: '#6366f1' },
chipEmoji: { fontSize: 14 },
chipText: { color: 'rgba(255,255,255,0.5)', fontSize: 13, fontWeight: '500' },
chipTextActive: { color: '#a5b4fc', fontWeight: '700' },
sortBar: {
flexDirection: 'row', alignItems: 'center', gap: 8,
paddingHorizontal: 16, marginTop: 12, marginBottom: 4,
},
sortLabel: { color: 'rgba(255,255,255,0.3)', fontSize: 12, marginRight: 4 },
sortBtn: {
paddingHorizontal: 10, paddingVertical: 5,
borderRadius: 8, backgroundColor: 'rgba(255,255,255,0.04)',
},
sortBtnActive: { backgroundColor: 'rgba(99,102,241,0.15)' },
sortBtnText: { color: 'rgba(255,255,255,0.4)', fontSize: 12 },
sortBtnTextActive: { color: '#a5b4fc', fontWeight: '600' },
count: { color: 'rgba(255,255,255,0.25)', fontSize: 12, marginHorizontal: 16, marginBottom: 12 },
card: {
marginHorizontal: 16, marginBottom: 12,
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 18, padding: 16,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
},
cardTop: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 },
cardMeta: { flexDirection: 'row', alignItems: 'center', gap: 6 },
statusDot: { width: 7, height: 7, borderRadius: 4 },
category: { color: 'rgba(255,255,255,0.35)', fontSize: 12, textTransform: 'capitalize' },
reward: { color: '#10b981', fontWeight: '800', fontSize: 16 },
title: { color: '#fff', fontWeight: '700', fontSize: 16, marginBottom: 6, lineHeight: 22 },
desc: { color: 'rgba(255,255,255,0.45)', fontSize: 14, lineHeight: 20, marginBottom: 12 },
cardFooter: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center' },
location: { color: 'rgba(255,255,255,0.3)', fontSize: 12 },
deadline: { color: '#f59e0b', fontSize: 12, fontWeight: '600' },
emptyWrap: { alignItems: 'center', marginTop: 60, paddingHorizontal: 32 },
emptyEmoji: { fontSize: 48, marginBottom: 16 },
emptyTitle: { color: '#fff', fontSize: 18, fontWeight: '700', marginBottom: 8 },
emptyText: { color: 'rgba(255,255,255,0.35)', fontSize: 14, textAlign: 'center', lineHeight: 20 },
})
+269
View File
@@ -0,0 +1,269 @@
/**
* ProfileScreen — zoomer-profil, stats, badges
*/
import { useCallback, useEffect, useState } from 'react'
import {
View, Text, ScrollView, TouchableOpacity,
StyleSheet, ActivityIndicator, Alert, RefreshControl,
} from 'react-native'
import { auth, profile, type Zoomer } from '../lib/api'
const LEVEL_LABELS: Record<number, string> = {
1: 'Rookie Zoomer',
2: 'Junior Zoomer',
3: 'Zoomer',
4: 'Senior Zoomer',
5: 'Elite Zoomer',
6: 'Legend Zoomer',
}
const LEVEL_COLOR: Record<number, string> = {
1: '#6b7280',
2: '#10b981',
3: '#3b82f6',
4: '#8b5cf6',
5: '#f59e0b',
6: '#ef4444',
}
function levelProgress(level: number, totalEarned: number): number {
// Simple XP curve: each level needs level*500 kr more
const levelThreshold = level * 50000 // in öre = level * 500 SEK
const prevThreshold = (level - 1) * 50000
const progress = (totalEarned - prevThreshold) / (levelThreshold - prevThreshold)
return Math.min(Math.max(progress, 0), 1)
}
export function ProfileScreen() {
const [zoomer, setZoomer] = useState<Zoomer | null>(null)
const [loading, setLoading] = useState(true)
const [refreshing, setRefreshing] = useState(false)
const load = useCallback(async () => {
try {
const data = await profile.get()
setZoomer(data)
} catch {
// silent
} finally {
setLoading(false)
setRefreshing(false)
}
}, [])
useEffect(() => { load() }, [])
const onRefresh = useCallback(() => { setRefreshing(true); load() }, [load])
const handleSignOut = () => {
Alert.alert(
'Logga ut',
'Är du säker på att du vill logga ut?',
[
{ text: 'Avbryt', style: 'cancel' },
{
text: 'Logga ut',
style: 'destructive',
onPress: async () => {
await auth.logout()
// AppNavigator will detect missing token and show AuthScreen
},
},
],
)
}
if (loading) {
return (
<View style={styles.center}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
const level = zoomer?.level ?? 1
const levelLabel = LEVEL_LABELS[level] ?? `Nivå ${level}`
const levelColor = LEVEL_COLOR[level] ?? '#6366f1'
const progress = zoomer ? levelProgress(level, zoomer.total_earned) : 0
const initial = (zoomer?.display_name ?? zoomer?.email ?? '?')[0].toUpperCase()
return (
<ScrollView
style={styles.container}
contentContainerStyle={{ paddingBottom: 40 }}
refreshControl={<RefreshControl refreshing={refreshing} onRefresh={onRefresh} tintColor="#6366f1" />}
>
{/* Avatar + name */}
<View style={styles.headerSection}>
<View style={[styles.avatar, { borderColor: levelColor }]}>
<Text style={styles.avatarText}>{initial}</Text>
</View>
<Text style={styles.displayName}>
{zoomer?.display_name || 'Namnlös zoomer'}
</Text>
<Text style={styles.email}>{zoomer?.email}</Text>
{/* Level badge */}
<View style={[styles.levelBadge, { backgroundColor: levelColor + '22', borderColor: levelColor + '55' }]}>
<Text style={[styles.levelBadgeText, { color: levelColor }]}>
{levelLabel}
</Text>
</View>
{/* XP bar */}
<View style={styles.xpBarWrap}>
<View style={styles.xpBarBg}>
<View style={[styles.xpBarFill, { width: `${(progress * 100).toFixed(0)}%` as any, backgroundColor: levelColor }]} />
</View>
<Text style={styles.xpLabel}>
Nivå {level} {level + 1} ({(progress * 100).toFixed(0)}%)
</Text>
</View>
</View>
{/* Stats */}
<View style={styles.statsGrid}>
<View style={styles.statCard}>
<Text style={styles.statValue}>
{zoomer ? (zoomer.total_earned / 100).toFixed(0) : '0'} kr
</Text>
<Text style={styles.statLabel}>Totalt intjänat</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: '#10b981' }]}>
{zoomer?.missions_completed ?? 0}
</Text>
<Text style={styles.statLabel}>Uppdrag klara</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: levelColor }]}>
{level}
</Text>
<Text style={styles.statLabel}>Nivå</Text>
</View>
<View style={styles.statCard}>
<Text style={[styles.statValue, { color: '#f59e0b' }]}>
{zoomer?.missions_completed ?? 0}
</Text>
<Text style={styles.statLabel}>Uppdrag</Text>
</View>
</View>
{/* Badges — kommer när backend har stöd */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Badges</Text>
<View style={styles.emptyBadges}>
<Text style={styles.emptyEmoji}>🏅</Text>
<Text style={styles.emptyText}>
Slutför ditt första uppdrag för att låsa upp din första badge!
</Text>
</View>
</View>
{/* Member since */}
{zoomer?.joined_at && (
<Text style={styles.memberSince}>
Zoomer sedan {new Date(zoomer.joined_at).toLocaleDateString('sv-SE', { month: 'long', year: 'numeric' })}
</Text>
)}
{/* Sign out */}
<View style={styles.section}>
<TouchableOpacity style={styles.signOutBtn} onPress={handleSignOut}>
<Text style={styles.signOutText}>Logga ut</Text>
</TouchableOpacity>
</View>
</ScrollView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
center: { flex: 1, backgroundColor: '#0A0A1B', alignItems: 'center', justifyContent: 'center' },
headerSection: {
alignItems: 'center',
paddingTop: 64,
paddingHorizontal: 24,
paddingBottom: 24,
borderBottomWidth: 1,
borderBottomColor: 'rgba(255,255,255,0.06)',
},
avatar: {
width: 88, height: 88, borderRadius: 44,
backgroundColor: 'rgba(99,102,241,0.3)',
alignItems: 'center', justifyContent: 'center',
marginBottom: 16,
borderWidth: 3,
},
avatarText: { color: '#fff', fontSize: 36, fontWeight: '800' },
displayName: { color: '#fff', fontSize: 22, fontWeight: '800', marginBottom: 4 },
email: { color: 'rgba(255,255,255,0.4)', fontSize: 14, marginBottom: 16 },
levelBadge: {
borderRadius: 20, paddingHorizontal: 16, paddingVertical: 8,
borderWidth: 1, marginBottom: 16,
},
levelBadgeText: { fontWeight: '700', fontSize: 13 },
xpBarWrap: { width: '100%', gap: 6 },
xpBarBg: {
height: 6, backgroundColor: 'rgba(255,255,255,0.08)',
borderRadius: 3, overflow: 'hidden',
},
xpBarFill: { height: '100%', borderRadius: 3 },
xpLabel: { color: 'rgba(255,255,255,0.3)', fontSize: 11, textAlign: 'center' },
statsGrid: {
flexDirection: 'row', flexWrap: 'wrap',
padding: 16, gap: 10,
},
statCard: {
width: '47%',
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 16, padding: 16,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
alignItems: 'center',
},
statValue: { color: '#fff', fontSize: 24, fontWeight: '800', marginBottom: 4 },
statLabel: { color: 'rgba(255,255,255,0.35)', fontSize: 12 },
section: { paddingHorizontal: 16, marginTop: 8 },
sectionTitle: {
color: 'rgba(255,255,255,0.35)', fontSize: 12, fontWeight: '700',
letterSpacing: 1, textTransform: 'uppercase', marginBottom: 12,
},
badgesGrid: { flexDirection: 'row', flexWrap: 'wrap', gap: 10 },
badgeCard: {
width: '30%',
backgroundColor: 'rgba(255,255,255,0.04)',
borderRadius: 14, padding: 12,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.07)',
alignItems: 'center', gap: 4,
},
badgeEmoji: { fontSize: 28 },
badgeLabel: { color: '#fff', fontSize: 11, fontWeight: '600', textAlign: 'center' },
badgeDate: { color: 'rgba(255,255,255,0.3)', fontSize: 9 },
emptyBadges: {
backgroundColor: 'rgba(255,255,255,0.03)',
borderRadius: 16, padding: 24,
alignItems: 'center', gap: 8,
borderWidth: 1, borderColor: 'rgba(255,255,255,0.06)',
},
emptyEmoji: { fontSize: 36 },
emptyText: { color: 'rgba(255,255,255,0.4)', fontSize: 13, textAlign: 'center', lineHeight: 20 },
memberSince: {
color: 'rgba(255,255,255,0.2)', fontSize: 12, textAlign: 'center',
marginTop: 16, marginBottom: 8,
},
signOutBtn: {
borderWidth: 1, borderColor: 'rgba(239,68,68,0.35)',
borderRadius: 14, paddingVertical: 14,
alignItems: 'center', marginTop: 8,
},
signOutText: { color: '#ef4444', fontWeight: '700', fontSize: 15 },
})
@@ -0,0 +1,340 @@
/**
* seed-sverige-launch.ts
*
* Skapar 50 seed-uppdrag i Stockholms skärgård för quiXzoom Sverige-launch (juni 2026).
* Koordinater runt Vaxholm, Sandhamn, Grinda, Möja och angränsande öar.
*
* Usage:
* QUIXZOOM_API_URL=https://api.quixzoom.com \
* QUIXZOOM_ADMIN_TOKEN=<admin-jwt> \
* npx ts-node scripts/seed-sverige-launch.ts
*/
const API_BASE = process.env.QUIXZOOM_API_URL ?? 'https://api.quixzoom.com'
const ADMIN_TOKEN = process.env.QUIXZOOM_ADMIN_TOKEN ?? ''
// ─── Geografiska ankarpunkter ──────────────────────────────────────────────
interface AnchorPoint {
name: string
lat: number
lng: number
}
const ANCHORS: AnchorPoint[] = [
{ name: 'Vaxholm', lat: 59.4028, lng: 18.3512 },
{ name: 'Sandhamn', lat: 59.2939, lng: 18.9127 },
{ name: 'Grinda', lat: 59.3544, lng: 18.6884 },
{ name: 'Möja', lat: 59.4456, lng: 18.7989 },
{ name: 'Utö', lat: 58.9697, lng: 17.9901 },
{ name: 'Fjäderholmarna', lat: 59.3295, lng: 18.1778 },
{ name: 'Värmdö', lat: 59.3241, lng: 18.5301 },
{ name: 'Nämdö', lat: 59.1936, lng: 18.8001 },
{ name: 'Runmarö', lat: 59.2581, lng: 18.7890 },
{ name: 'Ingarö', lat: 59.3102, lng: 18.4623 },
]
// ─── Kategorier & mallar ──────────────────────────────────────────────────
type Category = 'infrastruktur' | 'vägar' | 'hamnar' | 'bryggor' | 'fritidshus'
interface MissionTemplate {
category: Category
titles: string[]
descriptions: string[]
instructions: string
requirements: string[]
rewardRange: [number, number] // SEK
}
const TEMPLATES: MissionTemplate[] = [
{
category: 'hamnar',
titles: [
'Dokumentera hamninloppet',
'Hamnens parkeringsytor',
'Biljettbox och infoskyltning',
'Hamnkapacitet vid högsäsong',
'Gästhamns facilitetsstatus',
],
descriptions: [
'Fotografera hamninloppet från kajen och visa djupmarkeringar, bojar och eventuella hinder.',
'Dokumentera tillgängliga parkeringsplatser för bilar och båptrailers vid hamnen.',
'Fota biljettautomater, informationsskyltar och eventuella QR-koder i hamnen.',
'Visa antal båtplatser, beläggningsgrad och kösituation under högsäsong.',
'Dokumentera toaletter, duschar, sophantering och laddstationer för båtar.',
],
instructions: 'Ta minst 3 bilder från olika vinklar. Inkludera en vid bild och en närbild av skyltar/utrustning.',
requirements: [
'Minst 3 bilder krävs',
'Inkludera en översiktsbild',
'Tydliga bilder utan oskärpa',
],
rewardRange: [200, 350],
},
{
category: 'bryggor',
titles: [
'Brygga skick och bärighet',
'Förtöjningsutrustning',
'Färjebrygga dokumentation',
'Privat brygga tillståndskoll',
'Brygga efter vinterlagring',
],
descriptions: [
'Dokumentera bryggornas skick — räcken, plankor, belysning och eventuella skador.',
'Fotografera förtöjningspollare, ringar och eluttag längs bryggan.',
'Fota färjebryggan, rampen, väntutrymmen och informationsskyltar.',
'Dokumentera privat brygga vid angiven fastighet för försäkringsändamål.',
'Kontrollera bryggas skick efter vintersäsongen — lös plankor, rostiga delar, sättningar.',
],
instructions: 'Gå ut på bryggan om det är säkert. Ta bilder rakt ned för att visa plankors skick.',
requirements: [
'Bilder från bryggans kant och mitt',
'Närbild på eventuella skador',
'Inga personuppgifter i bild',
],
rewardRange: [150, 280],
},
{
category: 'vägar',
titles: [
'Vägunderhåll kontroll',
'Vägskyltning dokumentation',
'Grusväg efter tjällossning',
'Brovägsstatus',
'Vinterväghållning uppföljning',
],
descriptions: [
'Fotografera vägbeläggningens skick — sprickor, gropar, kantskador.',
'Dokumentera hastighetsskyltar, vägvisare och varningsmärken längs rutten.',
'Kontrollera och fotografera grusvägens skick efter vårens tjällossning.',
'Dokumentera brobanans skick, räcken och eventuella sättningar.',
'Fotografera vägens skick och snöröjning/sandning efter vinterperiod.',
],
instructions: 'Fotografera vägen i körfältets riktning. Inkludera referensobjekt för skala.',
requirements: [
'Minst 4 bilder längs sträckan',
'GPS-position ska matcha uppdraget',
'Inga bilar med läsbar registreringsskylt',
],
rewardRange: [180, 320],
},
{
category: 'infrastruktur',
titles: [
'Elnätsmast status',
'Avloppspumpstation',
'Vattenreservoar inspektion',
'Fiberkabel markering',
'Mobilmaster täckning',
],
descriptions: [
'Dokumentera elnätsmastens skick, eventuella skador och omgivande vegetation.',
'Fotografera avloppspumpstationen, larm och omgärdning.',
'Dokumentera vattenreservoarens yttre skick, lås och skyltning.',
'Markera och fotografera fiberkabelmarkeringar längs given sträcka.',
'Dokumentera mobilmastens placering, eventuella skador och omgivande byggnader.',
],
instructions: 'Fotografera enbart utsidan av anläggningar. Gå inte in i inhägnade områden.',
requirements: [
'Håll säkerhetsavstånd till elinstallationer',
'Fotografera ej inre av installationer',
'Inkludera omgivningsbild',
],
rewardRange: [250, 400],
},
{
category: 'fritidshus',
titles: [
'Fastighetsdokumentation exteriör',
'Stormskador efter höststormar',
'Renovering före/efter',
'Fastighetsläge och utsikt',
'Tomt och omgivning dokumentation',
],
descriptions: [
'Fotografera fastighetens exteriör från alla fyra sidor för försäkrings- och värderingsändamål.',
'Dokumentera eventuella stormskador på tak, fasad, fönster och omgivande träd.',
'Fotografera renoveringsarbeten på angiven fastighet för projektuppföljning.',
'Dokumentera fastighetens läge, utsikt, strandlinje och angränsande fastigheter.',
'Fotografera tomt, staket, sjöbod, brygga och angränsande skog/mark.',
],
instructions: 'Fotografera enbart utsidan. Inga interiörbilder. Respektera privatpersoners integritet.',
requirements: [
'Enbart exteriörbilder',
'Alla fyra fasader om möjligt',
'Inga privatpersoner i bild',
'GDPR-kompatibelt material',
],
rewardRange: [200, 380],
},
]
// ─── Hjälpfunktioner ───────────────────────────────────────────────────────
function randomBetween(min: number, max: number): number {
return Math.random() * (max - min) + min
}
function jitter(value: number, maxDelta: number): number {
return value + randomBetween(-maxDelta, maxDelta)
}
function pickRandom<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)]
}
function deadlineFromNow(hours: number): string {
return new Date(Date.now() + hours * 3600 * 1000).toISOString()
}
// ─── Uppdragsgenerator ────────────────────────────────────────────────────
interface SeedMission {
title: string
description: string
instructions: string
requirements: string[]
category: Category
latitude: number
longitude: number
city: string
reward_amount: number // öre
reward_currency: string
deadline_at: string
status: 'open'
max_submissions: number
tags: string[]
}
function generateMissions(count: number): SeedMission[] {
const missions: SeedMission[] = []
for (let i = 0; i < count; i++) {
const anchor = pickRandom(ANCHORS)
const template = pickRandom(TEMPLATES)
const titleIdx = Math.floor(Math.random() * template.titles.length)
const descIdx = Math.floor(Math.random() * template.descriptions.length)
// Sprid ut koordinaterna kring ankarpunkten (±0.05 grader ≈ ±5 km)
const lat = jitter(anchor.lat, 0.05)
const lng = jitter(anchor.lng, 0.08)
// Belöning i öre (SEK * 100), rundad till 50 kr steg
const rewardSEK = Math.round(randomBetween(
template.rewardRange[0],
template.rewardRange[1],
) / 50) * 50
const rewardOre = rewardSEK * 100
// Deadline: 2472 timmar
const deadlineHours = Math.floor(randomBetween(24, 72))
missions.push({
title: template.titles[titleIdx],
description: template.descriptions[descIdx],
instructions: template.instructions,
requirements: template.requirements,
category: template.category,
latitude: Math.round(lat * 100000) / 100000,
longitude: Math.round(lng * 100000) / 100000,
city: anchor.name,
reward_amount: rewardOre,
reward_currency: 'SEK',
deadline_at: deadlineFromNow(deadlineHours),
status: 'open',
max_submissions: Math.floor(randomBetween(1, 4)),
tags: ['sverige-launch-2026', 'stockholms-skärgård', anchor.name.toLowerCase()],
})
}
return missions
}
// ─── API POST ──────────────────────────────────────────────────────────────
async function postMission(mission: SeedMission): Promise<void> {
const res = await fetch(`${API_BASE}/missions`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${ADMIN_TOKEN}`,
},
body: JSON.stringify(mission),
})
if (!res.ok) {
const body = await res.text()
throw new Error(`HTTP ${res.status}: ${body}`)
}
const created = await res.json() as { id: string }
console.log(`${mission.city.padEnd(20)} [${mission.category.padEnd(14)}] ${(mission.reward_amount / 100)} SEK → id=${created.id}`)
}
// ─── Main ──────────────────────────────────────────────────────────────────
async function main() {
console.log('🇸🇪 quiXzoom Sverige-launch seed — 50 uppdrag i Stockholms skärgård\n')
if (!ADMIN_TOKEN) {
console.warn('⚠️ QUIXZOOM_ADMIN_TOKEN inte satt — kör i DRY-RUN-läge (skriver ej till API)\n')
}
const missions = generateMissions(50)
// Visa fördelning
const byCat: Record<string, number> = {}
const byCity: Record<string, number> = {}
for (const m of missions) {
byCat[m.category] = (byCat[m.category] ?? 0) + 1
byCity[m.city] = (byCity[m.city] ?? 0) + 1
}
console.log('Kategorifördelning:')
for (const [cat, n] of Object.entries(byCat).sort((a, b) => b[1] - a[1])) {
console.log(` ${cat.padEnd(20)} ${n} uppdrag`)
}
console.log('\nPlatsfördelning:')
for (const [city, n] of Object.entries(byCity).sort((a, b) => b[1] - a[1])) {
console.log(` ${city.padEnd(20)} ${n} uppdrag`)
}
const totalSEK = missions.reduce((s, m) => s + m.reward_amount / 100, 0)
const avgSEK = totalSEK / missions.length
console.log(`\nTotalt utlagt: ${totalSEK.toFixed(0)} SEK | Snitt: ${avgSEK.toFixed(0)} SEK/uppdrag`)
console.log(`Deadline-spann: 2472 timmar\n`)
if (!ADMIN_TOKEN) {
console.log('DRY-RUN: Visar första 3 uppdrag:')
for (const m of missions.slice(0, 3)) {
console.log(JSON.stringify(m, null, 2))
}
console.log('\nSätt QUIXZOOM_ADMIN_TOKEN för att posta till API.')
return
}
// Post i batchar om 5 med fördröjning
const BATCH_SIZE = 5
const DELAY_MS = 300
let success = 0
let failed = 0
for (let i = 0; i < missions.length; i += BATCH_SIZE) {
const batch = missions.slice(i, i + BATCH_SIZE)
await Promise.allSettled(
batch.map(m =>
postMission(m)
.then(() => { success++ })
.catch(e => { console.error(`${m.title}: ${e.message}`); failed++ }),
),
)
if (i + BATCH_SIZE < missions.length) {
await new Promise(r => setTimeout(r, DELAY_MS))
}
}
console.log(`\n✅ Klar! ${success} uppdrag skapade, ${failed} misslyckades.`)
console.log('🚀 Sverige-launch redo för juni 2026!')
}
main().catch(console.error)
-172
View File
@@ -1,172 +0,0 @@
#!/bin/bash
# quiXzoom Build Verification Script
# Verifies that all required files exist for TestFlight build
set -e
echo "🔍 quiXzoom Build Verification"
echo "================================"
echo ""
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
ERRORS=0
WARNINGS=0
check_file() {
if [ -f "$1" ]; then
echo -e "${GREEN}${NC} $1"
return 0
else
echo -e "${RED}${NC} $1 (MISSING)"
ERRORS=$((ERRORS + 1))
return 1
fi
}
check_dir() {
if [ -d "$1" ]; then
echo -e "${GREEN}${NC} $1/"
return 0
else
echo -e "${RED}${NC} $1/ (MISSING)"
ERRORS=$((ERRORS + 1))
return 1
fi
}
echo "📦 Core Files"
echo "-------------"
check_file "package.json"
check_file "app.json"
check_file "index.js"
check_file "App.js"
echo ""
echo "📱 iOS Project"
echo "--------------"
check_dir "ios"
check_file "ios/Podfile"
check_file "ios/quixzoom.xcodeproj/project.pbxproj"
check_file "ios/quixzoom/AppDelegate.h"
check_file "ios/quixzoom/AppDelegate.mm"
check_file "ios/quixzoom/main.m"
check_file "ios/quixzoom/Info.plist"
check_file "ios/quixzoom/LaunchScreen.storyboard"
check_file "ios/quixzoom/quixzoom.entitlements"
check_file "ios/quixzoom/Images.xcassets/Contents.json"
check_file "ios/quixzoom/Images.xcassets/AppIcon.appiconset/Contents.json"
check_file "ios/ExportOptions.plist"
echo ""
echo "🤖 Android Project"
echo "------------------"
check_dir "android"
check_file "android/build.gradle"
check_file "android/app/build.gradle"
check_file "android/app/src/main/AndroidManifest.xml"
echo ""
echo "📂 Source Files"
echo "---------------"
check_dir "src"
check_dir "src/api"
check_dir "src/components"
check_dir "src/hooks"
check_dir "src/navigation"
check_dir "src/screens"
check_dir "src/store"
check_dir "src/utils"
check_file "src/api/client.js"
check_file "src/components/CameraView.js"
check_file "src/components/EvidencePanel.js"
check_file "src/components/MissionCard.js"
check_file "src/components/StatsView.js"
check_file "src/hooks/useCamera.js"
check_file "src/hooks/useLocation.js"
check_file "src/hooks/useMissions.js"
check_file "src/navigation/AppNavigator.js"
check_file "src/screens/HomeScreen.js"
check_file "src/screens/StatsScreen.js"
check_file "src/screens/ProfileScreen.js"
check_file "src/store/store.js"
check_file "src/utils/permissions.js"
check_file "src/utils/offlineManager.js"
echo ""
echo "📋 Configuration Validation"
echo "---------------------------"
# Check bundle identifier
if grep -q "com.quixzoom.app" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} Bundle identifier: com.quixzoom.app"
else
echo -e "${YELLOW}⚠️${NC} Bundle identifier not found"
WARNINGS=$((WARNINGS + 1))
fi
# Check version
VERSION=$(grep "CFBundleShortVersionString" ios/quixzoom/Info.plist | head -1 | sed 's/.*<string>\(.*\)<\/string>.*/\1/')
echo -e "${GREEN}${NC} App version: $VERSION"
# Check minimum iOS version
MIN_IOS=$(grep "IPHONEOS_DEPLOYMENT_TARGET" ios/quixzoom.xcodeproj/project.pbxproj | head -1 | sed 's/.*= \(.*\);.*/\1/' | tr -d ' ')
echo -e "${GREEN}${NC} Minimum iOS version: $MIN_IOS"
# Check required device capabilities
if grep -q "gps" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} GPS capability declared"
else
echo -e "${RED}${NC} GPS capability missing"
ERRORS=$((ERRORS + 1))
fi
if grep -q "camera" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} Camera capability declared"
else
echo -e "${RED}${NC} Camera capability missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
echo "🔐 Permissions Check"
echo "--------------------"
if grep -q "NSCameraUsageDescription" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} Camera permission description"
else
echo -e "${RED}${NC} Camera permission description missing"
ERRORS=$((ERRORS + 1))
fi
if grep -q "NSLocationWhenInUseUsageDescription" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} Location permission description"
else
echo -e "${RED}${NC} Location permission description missing"
ERRORS=$((ERRORS + 1))
fi
if grep -q "NSPhotoLibraryUsageDescription" ios/quixzoom/Info.plist; then
echo -e "${GREEN}${NC} Photo library permission description"
else
echo -e "${RED}${NC} Photo library permission description missing"
ERRORS=$((ERRORS + 1))
fi
echo ""
echo "================================"
if [ $ERRORS -eq 0 ]; then
echo -e "${GREEN}✅ Build verification PASSED${NC}"
echo " $WARNINGS warnings"
exit 0
else
echo -e "${RED}❌ Build verification FAILED${NC}"
echo " $ERRORS errors, $WARNINGS warnings"
exit 1
fi
-181
View File
@@ -1,181 +0,0 @@
/**
* API Client
* HTTP client for quiXzoom API with offline support
*/
import axios from 'axios';
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
// API Configuration
const API_BASE_URL = 'https://api.quixzoom.com/v1';
const API_TIMEOUT = 30000;
// Create axios instance
const client = axios.create({
baseURL: API_BASE_URL,
timeout: API_TIMEOUT,
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
});
// Request interceptor
client.interceptors.request.use(
async (config) => {
// Add auth token
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
// Check network status
const netInfo = await NetInfo.fetch();
if (!netInfo.isConnected) {
// Queue request for later
await queueRequest(config);
throw new Error('OFFLINE');
}
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor
client.interceptors.response.use(
(response) => response,
async (error) => {
if (error.message === 'OFFLINE') {
return { data: { queued: true } };
}
// Handle 401 - Unauthorized
if (error.response?.status === 401) {
await AsyncStorage.removeItem('authToken');
// Trigger re-auth
}
return Promise.reject(error);
}
);
// Queue request for offline sync
async function queueRequest(config) {
const queue = await AsyncStorage.getItem('requestQueue');
const requests = queue ? JSON.parse(queue) : [];
requests.push({
id: Date.now().toString(),
url: config.url,
method: config.method,
data: config.data,
headers: config.headers,
timestamp: new Date().toISOString(),
});
await AsyncStorage.setItem('requestQueue', JSON.stringify(requests));
}
// Sync queued requests
export async function syncQueue() {
const queue = await AsyncStorage.getItem('requestQueue');
if (!queue) return { synced: 0, failed: 0 };
const requests = JSON.parse(queue);
let synced = 0;
let failed = 0;
for (const request of requests) {
try {
await client.request({
url: request.url,
method: request.method,
data: request.data,
headers: request.headers,
});
synced++;
} catch (error) {
failed++;
}
}
// Clear synced requests
await AsyncStorage.setItem('requestQueue', JSON.stringify([]));
return { synced, failed };
}
// Auth API
export const auth = {
register: (username, password, role = 'zoomer') =>
client.post('/auth/register', { username, password, role }),
login: (username, password) =>
client.post('/auth/login', { username, password }),
logout: () =>
client.post('/auth/logout'),
};
// Mission API
export const missions = {
list: (params = {}) =>
client.get('/missions', { params }),
get: (id) =>
client.get(`/missions/${id}`),
accept: (id) =>
client.post(`/missions/${id}/accept`),
complete: (id, data) =>
client.post(`/missions/${id}/complete`, data),
};
// Observation API
export const observations = {
list: (params = {}) =>
client.get('/observations', { params }),
create: (data) =>
client.post('/observations', data),
upload: (formData) =>
client.post('/observations/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' },
}),
analyze: (imagePath, imageId) =>
client.post('/visual-geolocation/analyze', { image_path: imagePath, image_id: imageId }),
};
// Stats API
export const stats = {
get: () =>
client.get('/stats'),
earnings: () =>
client.get('/stats/earnings'),
leaderboard: () =>
client.get('/stats/leaderboard'),
};
// User API
export const user = {
profile: () =>
client.get('/user/profile'),
update: (data) =>
client.put('/user/profile', data),
settings: () =>
client.get('/user/settings'),
updateSettings: (data) =>
client.put('/user/settings', data),
};
export default client;
@@ -1,308 +0,0 @@
/**
* CameraView Component
* Advanced camera with real-time evidence overlay
*/
import React, { useState, useEffect, useRef } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
Dimensions,
} from 'react-native';
import { Camera, useCameraDevices } from 'react-native-vision-camera';
import Icon from 'react-native-vector-icons/MaterialIcons';
const { width, height } = Dimensions.get('window');
export default function CameraView({ onCapture, mission, evidence }) {
const [hasPermission, setHasPermission] = useState(false);
const [isCapturing, setIsCapturing] = useState(false);
const [flashMode, setFlashMode] = useState('auto');
const [zoom, setZoom] = useState(0);
const camera = useRef(null);
const devices = useCameraDevices();
const device = devices.back;
const pulseAnim = useRef(new Animated.Value(1)).current;
// Pulse animation for capture button
useEffect(() => {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.1,
duration: 1000,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}),
])
);
pulse.start();
return () => pulse.stop();
}, []);
// Check permissions
useEffect(() => {
checkPermission();
}, []);
const checkPermission = async () => {
const status = await Camera.requestCameraPermission();
setHasPermission(status === 'authorized');
};
// Capture photo
const capturePhoto = async () => {
if (!camera.current || isCapturing) return;
setIsCapturing(true);
try {
const photo = await camera.current.takePhoto({
qualityPrioritization: 'quality',
flash: flashMode,
enableShutterSound: true,
});
onCapture(photo);
} catch (error) {
console.error('Capture failed:', error);
} finally {
setIsCapturing(false);
}
};
// Toggle flash
const toggleFlash = () => {
const modes = ['off', 'auto', 'on'];
const currentIndex = modes.indexOf(flashMode);
setFlashMode(modes[(currentIndex + 1) % modes.length]);
};
// Get flash icon
const getFlashIcon = () => {
switch (flashMode) {
case 'on': return 'flash-on';
case 'off': return 'flash-off';
default: return 'flash-auto';
}
};
if (!device) {
return (
<View style={styles.loadingContainer}>
<Text style={styles.loadingText}>Loading camera...</Text>
</View>
);
}
return (
<View style={styles.container}>
{/* Camera */}
<Camera
ref={camera}
style={styles.camera}
device={device}
isActive={true}
photo={true}
zoom={zoom}
/>
{/* Evidence Overlay */}
{evidence && (
<View style={styles.evidenceOverlay}>
<View style={styles.evidenceBadge}>
<Icon name="search" size={16} color="#fff" />
<Text style={styles.evidenceText}>
{evidence.visual_objects || 0} objects detected
</Text>
</View>
</View>
)}
{/* Mission Overlay */}
{mission && (
<View style={styles.missionOverlay}>
<View style={styles.missionCard}>
<Text style={styles.missionTitle}>{mission.title}</Text>
<Text style={styles.missionReward}>${mission.reward_usd}</Text>
</View>
</View>
)}
{/* Top Controls */}
<View style={styles.topControls}>
<TouchableOpacity onPress={toggleFlash} style={styles.controlButton}>
<Icon name={getFlashIcon()} size={24} color="#fff" />
</TouchableOpacity>
<TouchableOpacity
onPress={() => setZoom(zoom === 0 ? 0.5 : 0)}
style={styles.controlButton}
>
<Icon name={zoom > 0 ? 'zoom-out' : 'zoom-in'} size={24} color="#fff" />
</TouchableOpacity>
</View>
{/* Bottom Controls */}
<View style={styles.bottomControls}>
{/* Capture Button */}
<Animated.View style={[styles.captureButton, { transform: [{ scale: pulseAnim }] }]}>
<TouchableOpacity
onPress={capturePhoto}
disabled={isCapturing}
style={styles.captureInner}
>
{isCapturing ? (
<Icon name="hourglass-empty" size={32} color="#fff" />
) : (
<View style={styles.captureCircle} />
)}
</TouchableOpacity>
</Animated.View>
</View>
{/* Grid Overlay */}
<View style={styles.gridOverlay} pointerEvents="none">
<View style={styles.gridLine} />
<View style={[styles.gridLine, styles.gridLineVertical]} />
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
loadingContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#000',
},
loadingText: {
color: '#fff',
fontSize: 16,
},
camera: {
flex: 1,
},
evidenceOverlay: {
position: 'absolute',
top: 100,
left: 16,
},
evidenceBadge: {
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(0,122,255,0.8)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 16,
},
evidenceText: {
color: '#fff',
fontSize: 12,
fontWeight: '600',
marginLeft: 4,
},
missionOverlay: {
position: 'absolute',
top: 100,
right: 16,
},
missionCard: {
backgroundColor: 'rgba(52,199,89,0.9)',
padding: 12,
borderRadius: 12,
minWidth: 120,
},
missionTitle: {
color: '#fff',
fontSize: 12,
fontWeight: '600',
},
missionReward: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
marginTop: 4,
},
topControls: {
position: 'absolute',
top: 50,
left: 0,
right: 0,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
},
controlButton: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(0,0,0,0.5)',
justifyContent: 'center',
alignItems: 'center',
},
bottomControls: {
position: 'absolute',
bottom: 100,
left: 0,
right: 0,
alignItems: 'center',
},
captureButton: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: 'rgba(255,255,255,0.3)',
justifyContent: 'center',
alignItems: 'center',
},
captureInner: {
width: 70,
height: 70,
borderRadius: 35,
backgroundColor: '#fff',
justifyContent: 'center',
alignItems: 'center',
},
captureCircle: {
width: 60,
height: 60,
borderRadius: 30,
backgroundColor: '#fff',
},
gridOverlay: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
},
gridLine: {
position: 'absolute',
top: '33%',
left: 0,
right: 0,
height: 1,
backgroundColor: 'rgba(255,255,255,0.2)',
},
gridLineVertical: {
top: 0,
left: '33%',
width: 1,
height: '100%',
},
});
@@ -1,155 +0,0 @@
/**
* EvidencePanel Component
* Displays extracted evidence from image analysis
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function EvidencePanel({ evidence, confidence }) {
if (!evidence) return null;
const fadeAnim = new Animated.Value(0);
React.useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
}, [evidence]);
const renderEvidenceLayer = (icon, title, items, color) => (
<View style={[styles.layerCard, { borderLeftColor: color }]}>
<View style={styles.layerHeader}>
<Icon name={icon} size={20} color={color} />
<Text style={[styles.layerTitle, { color }]}>{title}</Text>
<Text style={styles.layerCount}>{items?.length || 0}</Text>
</View>
{items && items.map((item, index) => (
<Text key={index} style={styles.layerItem}> {item}</Text>
))}
</View>
);
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<ScrollView style={styles.scrollView}>
{/* Confidence Header */}
<View style={styles.confidenceHeader}>
<Text style={styles.confidenceTitle}>Evidence Analysis</Text>
<View style={styles.confidenceBadge}>
<Text style={styles.confidenceText}>
{(confidence * 100).toFixed(0)}% confidence
</Text>
</View>
</View>
{/* Evidence Layers */}
{renderEvidenceLayer('camera-alt', 'Metadata',
evidence.metadata ? [
`GPS: ${evidence.metadata.latitude?.toFixed(6)}, ${evidence.metadata.longitude?.toFixed(6)}`,
`Accuracy: ±${evidence.metadata.accuracy?.toFixed(0)}m`,
`Device: ${evidence.metadata.device_model}`,
] : [], '#007AFF')}
{renderEvidenceLayer('visibility', 'Visual Objects',
evidence.visual_objects?.map(obj => `${obj.label} (${(obj.confidence * 100).toFixed(0)}%)`), '#34C759')}
{renderEvidenceLayer('category', 'Semantic Objects',
evidence.semantic_objects?.map(obj => `${obj.label} (${(obj.confidence * 100).toFixed(0)}%)`), '#5856D6')}
{renderEvidenceLayer('text-fields', 'Text Detections',
evidence.text_detections?.map(t => t.text), '#FF9500')}
{renderEvidenceLayer('straighten', 'Geometric Features',
evidence.geometric_features ? [
`Horizon: ${evidence.geometric_features.horizon_detected ? 'Yes' : 'No'}`,
`Vanishing points: ${evidence.geometric_features.vanishing_points || 0}`,
] : [], '#AF52DE')}
{renderEvidenceLayer('wb-sunny', 'Environmental',
evidence.environmental_signals ? [
`Time: ${evidence.environmental_signals.time_of_day}`,
`Lighting: ${evidence.environmental_signals.lighting_quality}`,
`Weather: ${evidence.environmental_signals.weather || 'Unknown'}`,
] : [], '#FF3B30')}
{renderEvidenceLayer('history', 'Temporal',
evidence.temporal_signals ? [
`Season: ${evidence.temporal_signals.season}`,
`Day: ${evidence.temporal_signals.day_of_week}`,
] : [], '#5AC8FA')}
</ScrollView>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
scrollView: {
flex: 1,
padding: 16,
},
confidenceHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
},
confidenceTitle: {
fontSize: 20,
fontWeight: '700',
color: '#fff',
},
confidenceBadge: {
backgroundColor: 'rgba(0,122,255,0.3)',
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 16,
},
confidenceText: {
color: '#007AFF',
fontSize: 14,
fontWeight: '600',
},
layerCard: {
backgroundColor: '#1C1C1E',
borderRadius: 12,
padding: 16,
marginBottom: 12,
borderLeftWidth: 4,
},
layerHeader: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
layerTitle: {
fontSize: 16,
fontWeight: '600',
marginLeft: 8,
flex: 1,
},
layerCount: {
fontSize: 14,
color: '#8E8E93',
fontWeight: '600',
},
layerItem: {
fontSize: 14,
color: '#8E8E93',
marginLeft: 28,
marginBottom: 4,
},
});
@@ -1,260 +0,0 @@
/**
* MissionCard Component
* Displays available missions with reward and progress
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function MissionCard({ mission, onPress, isActive }) {
const scaleAnim = new Animated.Value(1);
const handlePressIn = () => {
Animated.spring(scaleAnim, {
toValue: 0.97,
useNativeDriver: true,
}).start();
};
const handlePressOut = () => {
Animated.spring(scaleAnim, {
toValue: 1,
useNativeDriver: true,
}).start();
};
// Calculate progress
const progress = mission.completed_observations / mission.total_observations;
const progressPercent = Math.round(progress * 100);
// Get category icon
const getCategoryIcon = (category) => {
const icons = {
infrastructure: 'construction',
building: 'business',
road: 'directions-car',
bridge: 'terrain',
environment: 'nature',
safety: 'security',
default: 'assignment',
};
return icons[category] || icons.default;
};
// Get difficulty color
const getDifficultyColor = (difficulty) => {
const colors = {
easy: '#34C759',
medium: '#FF9500',
hard: '#FF3B30',
};
return colors[difficulty] || colors.medium;
};
return (
<Animated.View style={{ transform: [{ scale: scaleAnim }] }}>
<TouchableOpacity
style={[styles.card, isActive && styles.activeCard]}
onPress={() => onPress(mission)}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
activeOpacity={0.9}
>
{/* Header */}
<View style={styles.header}>
<View style={styles.iconContainer}>
<Icon
name={getCategoryIcon(mission.category)}
size={24}
color="#007AFF"
/>
</View>
<View style={styles.headerText}>
<Text style={styles.title} numberOfLines={1}>
{mission.title}
</Text>
<Text style={styles.category}>{mission.category}</Text>
</View>
<View style={[styles.difficulty, { backgroundColor: getDifficultyColor(mission.difficulty) }]}>
<Text style={styles.difficultyText}>{mission.difficulty}</Text>
</View>
</View>
{/* Description */}
<Text style={styles.description} numberOfLines={2}>
{mission.description}
</Text>
{/* Progress Bar */}
<View style={styles.progressContainer}>
<View style={styles.progressBar}>
<View style={[styles.progressFill, { width: `${progressPercent}%` }]} />
</View>
<Text style={styles.progressText}>{progressPercent}%</Text>
</View>
{/* Footer */}
<View style={styles.footer}>
<View style={styles.footerItem}>
<Icon name="attach-money" size={16} color="#34C759" />
<Text style={styles.reward}>${mission.reward_usd}</Text>
</View>
<View style={styles.footerItem}>
<Icon name="location-on" size={16} color="#8E8E93" />
<Text style={styles.distance}>
{mission.distance ? `${mission.distance.toFixed(1)} km` : 'Remote'}
</Text>
</View>
<View style={styles.footerItem}>
<Icon name="schedule" size={16} color="#8E8E93" />
<Text style={styles.deadline}>
{new Date(mission.deadline).toLocaleDateString()}
</Text>
</View>
</View>
{/* Active Indicator */}
{isActive && (
<View style={styles.activeIndicator}>
<Icon name="check-circle" size={20} color="#34C759" />
<Text style={styles.activeText}>Active</Text>
</View>
)}
</TouchableOpacity>
</Animated.View>
);
}
const styles = StyleSheet.create({
card: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 16,
marginBottom: 12,
borderWidth: 1,
borderColor: '#2C2C2E',
},
activeCard: {
borderColor: '#34C759',
borderWidth: 2,
},
header: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
iconContainer: {
width: 44,
height: 44,
borderRadius: 12,
backgroundColor: 'rgba(0,122,255,0.1)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
headerText: {
flex: 1,
},
title: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
},
category: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
textTransform: 'capitalize',
},
difficulty: {
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
},
difficultyText: {
color: '#fff',
fontSize: 10,
fontWeight: '700',
textTransform: 'uppercase',
},
description: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 12,
lineHeight: 20,
},
progressContainer: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 12,
},
progressBar: {
flex: 1,
height: 6,
backgroundColor: '#2C2C2E',
borderRadius: 3,
marginRight: 8,
},
progressFill: {
height: '100%',
backgroundColor: '#34C759',
borderRadius: 3,
},
progressText: {
fontSize: 12,
color: '#8E8E93',
fontWeight: '600',
minWidth: 35,
},
footer: {
flexDirection: 'row',
justifyContent: 'space-between',
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
paddingTop: 12,
},
footerItem: {
flexDirection: 'row',
alignItems: 'center',
},
reward: {
fontSize: 14,
fontWeight: '700',
color: '#34C759',
marginLeft: 4,
},
distance: {
fontSize: 12,
color: '#8E8E93',
marginLeft: 4,
},
deadline: {
fontSize: 12,
color: '#8E8E93',
marginLeft: 4,
},
activeIndicator: {
position: 'absolute',
top: 12,
right: 12,
flexDirection: 'row',
alignItems: 'center',
backgroundColor: 'rgba(52,199,89,0.2)',
paddingHorizontal: 8,
paddingVertical: 4,
borderRadius: 8,
},
activeText: {
color: '#34C759',
fontSize: 10,
fontWeight: '700',
marginLeft: 4,
},
});
@@ -1,368 +0,0 @@
/**
* StatsView Component
* Zoomer statistics and earnings dashboard
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
ScrollView,
Animated,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function StatsView({ stats }) {
const fadeAnim = new Animated.Value(0);
React.useEffect(() => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 600,
useNativeDriver: true,
}).start();
}, []);
const renderStatCard = (icon, title, value, subtitle, color) => (
<View style={[styles.statCard, { borderLeftColor: color }]}>
<View style={styles.statIconContainer}>
<Icon name={icon} size={24} color={color} />
</View>
<View style={styles.statContent}>
<Text style={styles.statValue}>{value}</Text>
<Text style={styles.statTitle}>{title}</Text>
{subtitle && <Text style={styles.statSubtitle}>{subtitle}</Text>}
</View>
</View>
);
const renderLevelProgress = () => {
const levels = ['Supplementary', 'Active', 'Professional'];
const currentLevelIndex = levels.indexOf(stats.level);
const nextLevel = levels[currentLevelIndex + 1];
const progress = stats.level_progress || 0;
return (
<View style={styles.levelCard}>
<View style={styles.levelHeader}>
<Text style={styles.levelTitle}>Zoomer Level</Text>
<View style={[styles.levelBadge, { backgroundColor: getLevelColor(stats.level) }]}>
<Text style={styles.levelBadgeText}>{stats.level}</Text>
</View>
</View>
<View style={styles.levelProgressContainer}>
<View style={styles.levelProgressBar}>
<View style={[styles.levelProgressFill, { width: `${progress}%` }]} />
</View>
<Text style={styles.levelProgressText}>{progress}% to {nextLevel || 'Max'}</Text>
</View>
<View style={styles.levelRequirements}>
<Text style={styles.levelRequirementsTitle}>Requirements for {nextLevel}:</Text>
{getLevelRequirements(nextLevel).map((req, index) => (
<View key={index} style={styles.requirementRow}>
<Icon
name={req.completed ? 'check-circle' : 'radio-button-unchecked'}
size={16}
color={req.completed ? '#34C759' : '#8E8E93'}
/>
<Text style={[styles.requirementText, req.completed && styles.requirementCompleted]}>
{req.text}
</Text>
</View>
))}
</View>
</View>
);
};
const getLevelColor = (level) => {
const colors = {
'Supplementary': '#8E8E93',
'Active': '#007AFF',
'Professional': '#34C759',
};
return colors[level] || '#8E8E93';
};
const getLevelRequirements = (level) => {
const requirements = {
'Active': [
{ text: 'Complete 10 missions', completed: (stats.completed_missions || 0) >= 10 },
{ text: 'Earn $500 total', completed: (stats.total_earnings || 0) >= 500 },
{ text: '95% approval rate', completed: (stats.approval_rate || 0) >= 95 },
],
'Professional': [
{ text: 'Complete 50 missions', completed: (stats.completed_missions || 0) >= 50 },
{ text: 'Earn $5,000 total', completed: (stats.total_earnings || 0) >= 5000 },
{ text: '98% approval rate', completed: (stats.approval_rate || 0) >= 98 },
{ text: '5+ countries', completed: (stats.countries || 0) >= 5 },
],
};
return requirements[level] || [];
};
return (
<Animated.View style={[styles.container, { opacity: fadeAnim }]}>
<ScrollView style={styles.scrollView}>
{/* Header */}
<View style={styles.header}>
<Text style={styles.headerTitle}>Your Stats</Text>
<Text style={styles.headerSubtitle}>Last 30 days</Text>
</View>
{/* Earnings Card */}
<View style={styles.earningsCard}>
<Text style={styles.earningsLabel}>Total Earnings</Text>
<Text style={styles.earningsValue}>${(stats.total_earnings || 0).toFixed(2)}</Text>
<View style={styles.earningsTrend}>
<Icon name="trending-up" size={16} color="#34C759" />
<Text style={styles.earningsTrendText}>+{(stats.earnings_growth || 0).toFixed(1)}% this month</Text>
</View>
</View>
{/* Stats Grid */}
<View style={styles.statsGrid}>
{renderStatCard('camera-alt', 'Observations', stats.total_observations || 0, `${stats.approved_observations || 0} approved`, '#007AFF')}
{renderStatCard('check-circle', 'Missions', stats.completed_missions || 0, `${stats.active_missions || 0} active`, '#34C759')}
{renderStatCard('star', 'Rating', `${stats.rating || 0}/5.0`, `${stats.reviews || 0} reviews`, '#FF9500')}
{renderStatCard('schedule', 'Response Time', `${stats.avg_response_time || 0}h`, 'Average', '#5856D6')}
</View>
{/* Level Progress */}
{renderLevelProgress()}
{/* Recent Activity */}
<View style={styles.activityCard}>
<Text style={styles.activityTitle}>Recent Activity</Text>
{(stats.recent_activity || []).map((activity, index) => (
<View key={index} style={styles.activityRow}>
<View style={[styles.activityIcon, { backgroundColor: activity.color || '#007AFF20' }]}>
<Icon name={activity.icon || 'circle'} size={16} color={activity.color || '#007AFF'} />
</View>
<View style={styles.activityContent}>
<Text style={styles.activityText}>{activity.text}</Text>
<Text style={styles.activityTime}>{activity.time}</Text>
</View>
{activity.amount && (
<Text style={[styles.activityAmount, { color: activity.amount > 0 ? '#34C759' : '#FF3B30' }]}>
{activity.amount > 0 ? '+' : ''}${Math.abs(activity.amount).toFixed(2)}
</Text>
)}
</View>
))}
</View>
</ScrollView>
</Animated.View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
scrollView: {
flex: 1,
padding: 16,
},
header: {
marginBottom: 20,
},
headerTitle: {
fontSize: 28,
fontWeight: '700',
color: '#fff',
},
headerSubtitle: {
fontSize: 14,
color: '#8E8E93',
marginTop: 4,
},
earningsCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
alignItems: 'center',
},
earningsLabel: {
fontSize: 14,
color: '#8E8E93',
marginBottom: 8,
},
earningsValue: {
fontSize: 48,
fontWeight: '700',
color: '#34C759',
},
earningsTrend: {
flexDirection: 'row',
alignItems: 'center',
marginTop: 8,
},
earningsTrendText: {
fontSize: 14,
color: '#34C759',
marginLeft: 4,
},
statsGrid: {
flexDirection: 'row',
flexWrap: 'wrap',
justifyContent: 'space-between',
marginBottom: 16,
},
statCard: {
width: '48%',
backgroundColor: '#1C1C1E',
borderRadius: 12,
padding: 16,
marginBottom: 12,
borderLeftWidth: 3,
flexDirection: 'row',
alignItems: 'center',
},
statIconContainer: {
width: 40,
height: 40,
borderRadius: 10,
backgroundColor: 'rgba(255,255,255,0.05)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
statContent: {
flex: 1,
},
statValue: {
fontSize: 24,
fontWeight: '700',
color: '#fff',
},
statTitle: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
},
statSubtitle: {
fontSize: 11,
color: '#34C759',
marginTop: 2,
},
levelCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
},
levelHeader: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 12,
},
levelTitle: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
},
levelBadge: {
paddingHorizontal: 10,
paddingVertical: 4,
borderRadius: 8,
},
levelBadgeText: {
color: '#fff',
fontSize: 12,
fontWeight: '700',
},
levelProgressContainer: {
marginBottom: 16,
},
levelProgressBar: {
height: 8,
backgroundColor: '#2C2C2E',
borderRadius: 4,
marginBottom: 8,
},
levelProgressFill: {
height: '100%',
backgroundColor: '#34C759',
borderRadius: 4,
},
levelProgressText: {
fontSize: 12,
color: '#8E8E93',
textAlign: 'right',
},
levelRequirements: {
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
paddingTop: 12,
},
levelRequirementsTitle: {
fontSize: 14,
fontWeight: '600',
color: '#fff',
marginBottom: 8,
},
requirementRow: {
flexDirection: 'row',
alignItems: 'center',
marginBottom: 8,
},
requirementText: {
fontSize: 13,
color: '#8E8E93',
marginLeft: 8,
},
requirementCompleted: {
color: '#34C759',
textDecorationLine: 'line-through',
},
activityCard: {
backgroundColor: '#1C1C1E',
borderRadius: 16,
padding: 20,
marginBottom: 16,
},
activityTitle: {
fontSize: 16,
fontWeight: '600',
color: '#fff',
marginBottom: 12,
},
activityRow: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 10,
borderBottomWidth: 1,
borderBottomColor: '#2C2C2E',
},
activityIcon: {
width: 32,
height: 32,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
activityContent: {
flex: 1,
},
activityText: {
fontSize: 14,
color: '#fff',
},
activityTime: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
},
activityAmount: {
fontSize: 14,
fontWeight: '600',
},
});
@@ -0,0 +1,210 @@
/**
* ActiveMissionMap.tsx — Mission map with live tracking, auto-follow, distance/heading overlay
* Wraps react-native-maps with live location integration
*/
import { useEffect, useRef, useCallback } from 'react'
import { View, Text, StyleSheet } from 'react-native'
import MapView, { Marker, Region, PROVIDER_DEFAULT } from 'react-native-maps'
import { useLocationStore } from '../../stores/locationStore'
import { UserLocationMarker } from './UserLocationMarker'
import { FollowUserButton } from './FollowUserButton'
import type { Mission } from '../../../lib/api'
interface ActiveMissionMapProps {
mission?: Mission
style?: object
}
export function ActiveMissionMap({ mission, style }: ActiveMissionMapProps) {
const mapRef = useRef<MapView>(null)
const {
currentPosition,
heading,
followUser,
setUserPannedMap,
} = useLocationStore()
// Auto-follow user when followUser is true
useEffect(() => {
if (followUser && currentPosition && mapRef.current) {
const region: Region = {
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
latitudeDelta: 0.01,
longitudeDelta: 0.01,
}
mapRef.current.animateToRegion(region, 300)
}
}, [currentPosition, followUser])
const onPanDrag = useCallback(() => {
setUserPannedMap(true)
}, [setUserPannedMap])
// Calculate distance and bearing to mission
const getDistanceToMission = (): string => {
if (!currentPosition || !mission) return '—'
const R = 6371e3 // Earth radius in meters
const φ1 = (currentPosition.coords.latitude * Math.PI) / 180
const φ2 = (mission.latitude * Math.PI) / 180
const Δφ = ((mission.latitude - currentPosition.coords.latitude) * Math.PI) / 180
const Δλ = ((mission.longitude - currentPosition.coords.longitude) * Math.PI) / 180
const a =
Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2)
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a))
const distance = R * c
if (distance < 1000) {
return `${Math.round(distance)} m`
}
return `${(distance / 1000).toFixed(1)} km`
}
const getBearingToMission = (): number | null => {
if (!currentPosition || !mission) return null
const φ1 = (currentPosition.coords.latitude * Math.PI) / 180
const φ2 = (mission.latitude * Math.PI) / 180
const Δλ = ((mission.longitude - currentPosition.coords.longitude) * Math.PI) / 180
const y = Math.sin(Δλ) * Math.cos(φ2)
const x =
Math.cos(φ1) * Math.sin(φ2) -
Math.sin(φ1) * Math.cos(φ2) * Math.cos(Δλ)
const θ = Math.atan2(y, x)
const bearing = ((θ * 180) / Math.PI + 360) % 360
return bearing
}
const bearing = getBearingToMission()
const relativeBearing = bearing != null && heading != null
? ((bearing - heading + 360) % 360)
: null
return (
<View style={[styles.container, style]}>
<MapView
ref={mapRef}
style={styles.map}
provider={PROVIDER_DEFAULT}
showsUserLocation={false} // We use custom marker
showsMyLocationButton={false}
onPanDrag={onPanDrag}
rotateEnabled={false} // Map stays north-up, marker rotates
>
{/* Custom user location marker */}
{currentPosition && (
<Marker
coordinate={{
latitude: currentPosition.coords.latitude,
longitude: currentPosition.coords.longitude,
}}
anchor={{ x: 0.5, y: 0.5 }}
flat={true}
>
<UserLocationMarker />
</Marker>
)}
{/* Mission marker */}
{mission && (
<Marker
coordinate={{
latitude: mission.latitude,
longitude: mission.longitude,
}}
pinColor="#10b981"
title={mission.title}
/>
)}
</MapView>
{/* Distance / heading overlay */}
{mission && (
<View style={styles.overlay}>
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Avstånd</Text>
<Text style={styles.overlayValue}>{getDistanceToMission()}</Text>
</View>
{relativeBearing != null && (
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Riktning</Text>
<Text style={styles.overlayValue}>
{getDirectionArrow(relativeBearing)} {relativeBearing.toFixed(0)}°
</Text>
</View>
)}
{heading != null && (
<View style={styles.overlayRow}>
<Text style={styles.overlayLabel}>Din kurs</Text>
<Text style={styles.overlayValue}>{heading.toFixed(0)}°</Text>
</View>
)}
</View>
)}
{/* Follow user button */}
<View style={styles.followButtonContainer}>
<FollowUserButton />
</View>
</View>
)
}
function getDirectionArrow(degrees: number): string {
// Convert bearing to cardinal direction arrow
if (degrees >= 337.5 || degrees < 22.5) return '↑'
if (degrees >= 22.5 && degrees < 67.5) return '↗'
if (degrees >= 67.5 && degrees < 112.5) return '→'
if (degrees >= 112.5 && degrees < 157.5) return '↘'
if (degrees >= 157.5 && degrees < 202.5) return '↓'
if (degrees >= 202.5 && degrees < 247.5) return '↙'
if (degrees >= 247.5 && degrees < 292.5) return '←'
return '↖'
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
map: {
flex: 1,
},
overlay: {
position: 'absolute',
top: 16,
left: 16,
backgroundColor: 'rgba(10,10,27,0.85)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 140,
},
overlayRow: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
overlayLabel: {
color: 'rgba(255,255,255,0.45)',
fontSize: 11,
marginRight: 12,
},
overlayValue: {
color: '#fff',
fontSize: 13,
fontWeight: '700',
},
followButtonContainer: {
position: 'absolute',
bottom: 100,
right: 16,
},
})
@@ -0,0 +1,61 @@
/**
* FollowUserButton.tsx — Toggle follow/nofollow, return to follow after pan
* State managed in locationStore
*/
import { TouchableOpacity, Text, StyleSheet } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
interface FollowUserButtonProps {
onPress?: () => void
}
export function FollowUserButton({ onPress }: FollowUserButtonProps) {
const { followUser, setFollowUser } = useLocationStore()
const handlePress = () => {
const newFollow = !followUser
setFollowUser(newFollow)
onPress?.()
}
return (
<TouchableOpacity
style={[styles.button, followUser && styles.buttonActive]}
onPress={handlePress}
activeOpacity={0.8}
>
<Text style={[styles.icon, followUser && styles.iconActive]}>
{followUser ? '📍' : '📌'}
</Text>
</TouchableOpacity>
)
}
const styles = StyleSheet.create({
button: {
width: 44,
height: 44,
borderRadius: 22,
backgroundColor: 'rgba(10,10,27,0.9)',
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.15)',
alignItems: 'center',
justifyContent: 'center',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.3,
shadowRadius: 4,
elevation: 4,
},
buttonActive: {
backgroundColor: 'rgba(99,102,241,0.3)',
borderColor: '#6366f1',
},
icon: {
fontSize: 20,
opacity: 0.6,
},
iconActive: {
opacity: 1,
},
})
@@ -0,0 +1,145 @@
/**
* GpsDiagnostics.tsx — Developer overlay for GPS metrics
* Semi-transparent overlay, toggle via settings
*/
import { View, Text, StyleSheet } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
export function GpsDiagnostics() {
const {
currentPosition,
heading,
headingAccuracy,
accuracy,
speed,
isTracking,
isStale,
lastUpdateTimestamp,
updateIntervalMs,
locationAuthStatus,
} = useLocationStore()
const locationAge = lastUpdateTimestamp
? ((Date.now() - lastUpdateTimestamp) / 1000).toFixed(1)
: '—'
const speedKmh = speed != null ? (speed * 3.6).toFixed(1) : '—'
return (
<View style={styles.container}>
<View style={styles.panel}>
<Text style={styles.title}>🛰 GPS Diagnostics</Text>
<View style={styles.row}>
<Text style={styles.label}>Tracking</Text>
<Text style={[styles.value, isTracking ? styles.good : styles.bad]}>
{isTracking ? '● LIVE' : '○ OFF'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Auth Status</Text>
<Text style={styles.value}>{locationAuthStatus ?? '—'}</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>GPS Accuracy</Text>
<Text style={[styles.value, getAccuracyColor(accuracy)]}>
{accuracy != null ? `${accuracy.toFixed(1)} m` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Heading</Text>
<Text style={styles.value}>
{heading != null ? `${heading.toFixed(1)}°` : '—'}
{headingAccuracy != null ? `${headingAccuracy.toFixed(0)}°)` : ''}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Speed</Text>
<Text style={styles.value}>
{speed != null ? `${speed.toFixed(1)} m/s (${speedKmh} km/h)` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Update Interval</Text>
<Text style={styles.value}>
{updateIntervalMs != null ? `${updateIntervalMs} ms` : '—'}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Location Age</Text>
<Text style={[styles.value, isStale ? styles.bad : styles.good]}>
{locationAge}s {isStale ? '(STALE)' : ''}
</Text>
</View>
<View style={styles.row}>
<Text style={styles.label}>Coords</Text>
<Text style={styles.value} numberOfLines={1}>
{currentPosition
? `${currentPosition.coords.latitude.toFixed(6)}, ${currentPosition.coords.longitude.toFixed(6)}`
: '—'}
</Text>
</View>
</View>
</View>
)
}
function getAccuracyColor(accuracy: number | null): object {
if (accuracy == null) return styles.neutral
if (accuracy <= 5) return styles.excellent
if (accuracy <= 10) return styles.good
if (accuracy <= 20) return styles.warning
return styles.bad
}
const styles = StyleSheet.create({
container: {
position: 'absolute',
top: 100,
right: 8,
zIndex: 999,
},
panel: {
backgroundColor: 'rgba(0,0,0,0.75)',
borderRadius: 12,
padding: 12,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
minWidth: 220,
},
title: {
color: '#fff',
fontSize: 12,
fontWeight: '800',
marginBottom: 8,
letterSpacing: 0.5,
},
row: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 4,
},
label: {
color: 'rgba(255,255,255,0.45)',
fontSize: 10,
},
value: {
color: '#fff',
fontSize: 10,
fontWeight: '600',
maxWidth: 140,
},
excellent: { color: '#10b981' },
good: { color: '#34d399' },
warning: { color: '#f59e0b' },
bad: { color: '#ef4444' },
neutral: { color: 'rgba(255,255,255,0.5)' },
})
@@ -0,0 +1,154 @@
/**
* UserLocationMarker.tsx — Animated user location marker with heading rotation
* Includes accuracy circle (pulsing) and smooth heading animation
*/
import { useEffect, useRef } from 'react'
import { View, StyleSheet, Animated } from 'react-native'
import { useLocationStore } from '../../stores/locationStore'
export function UserLocationMarker() {
const { heading, accuracy } = useLocationStore()
const rotationAnim = useRef(new Animated.Value(0)).current
const pulseAnim = useRef(new Animated.Value(1)).current
// Smooth heading rotation
useEffect(() => {
if (heading != null) {
Animated.spring(rotationAnim, {
toValue: heading,
useNativeDriver: true,
friction: 8,
tension: 40,
}).start()
}
}, [heading, rotationAnim])
// Pulsing accuracy circle
useEffect(() => {
const pulse = Animated.loop(
Animated.sequence([
Animated.timing(pulseAnim, {
toValue: 1.3,
duration: 1500,
useNativeDriver: true,
}),
Animated.timing(pulseAnim, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
}),
])
)
pulse.start()
return () => {
pulse.stop()
}
}, [pulseAnim])
const spin = rotationAnim.interpolate({
inputRange: [0, 360],
outputRange: ['0deg', '360deg'],
})
const accuracyRadius = accuracy ?? 10
return (
<View style={styles.container}>
{/* Accuracy circle (pulsing) */}
<Animated.View
style={[
styles.accuracyCircle,
{
width: accuracyRadius * 2,
height: accuracyRadius * 2,
borderRadius: accuracyRadius,
transform: [{ scale: pulseAnim }],
opacity: pulseAnim.interpolate({
inputRange: [1, 1.3],
outputRange: [0.3, 0.1],
}),
},
]}
/>
{/* Static accuracy ring */}
<View
style={[
styles.accuracyRing,
{
width: accuracyRadius * 2,
height: accuracyRadius * 2,
borderRadius: accuracyRadius,
},
]}
/>
{/* User dot */}
<View style={styles.dot}>
<View style={styles.dotInner} />
</View>
{/* Heading arrow */}
<Animated.View style={[styles.arrow, { transform: [{ rotate: spin }] }]}>
<View style={styles.arrowShape} />
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
container: {
width: 60,
height: 60,
alignItems: 'center',
justifyContent: 'center',
},
accuracyCircle: {
position: 'absolute',
backgroundColor: 'rgba(99,102,241,0.3)',
},
accuracyRing: {
position: 'absolute',
borderWidth: 1,
borderColor: 'rgba(99,102,241,0.4)',
backgroundColor: 'transparent',
},
dot: {
width: 18,
height: 18,
borderRadius: 9,
backgroundColor: '#fff',
borderWidth: 3,
borderColor: '#6366f1',
alignItems: 'center',
justifyContent: 'center',
zIndex: 2,
},
dotInner: {
width: 6,
height: 6,
borderRadius: 3,
backgroundColor: '#6366f1',
},
arrow: {
position: 'absolute',
width: 40,
height: 40,
alignItems: 'center',
justifyContent: 'flex-start',
zIndex: 1,
},
arrowShape: {
width: 0,
height: 0,
backgroundColor: 'transparent',
borderStyle: 'solid',
borderLeftWidth: 6,
borderRightWidth: 6,
borderBottomWidth: 14,
borderLeftColor: 'transparent',
borderRightColor: 'transparent',
borderBottomColor: 'rgba(99,102,241,0.85)',
marginTop: -4,
},
})
@@ -0,0 +1,7 @@
/**
* GPS Components barrel export
*/
export { GpsDiagnostics } from './GpsDiagnostics'
export { UserLocationMarker } from './UserLocationMarker'
export { FollowUserButton } from './FollowUserButton'
export { ActiveMissionMap } from './ActiveMissionMap'
@@ -0,0 +1,204 @@
/**
* AuthScreen — JWT-baserad login/signup (identity-core, ej Supabase)
*/
import { useState } from 'react'
import {
View,
Text,
TextInput,
TouchableOpacity,
StyleSheet,
KeyboardAvoidingView,
Platform,
Alert,
ScrollView,
} from 'react-native'
import { auth } from '../../../lib/api'
interface Props {
onLoginSuccess: () => void
}
export function AuthScreen({ onLoginSuccess }: Props) {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [displayName, setDisplayName] = useState('')
const [loading, setLoading] = useState(false)
const [mode, setMode] = useState<'login' | 'signup'>('login')
const submit = async () => {
if (!email.trim() || !password) return
setLoading(true)
try {
if (mode === 'login') {
await auth.login(email.trim(), password)
} else {
if (password.length < 8) {
Alert.alert('Lösenord för kort', 'Minst 8 tecken krävs.')
return
}
await auth.register(email.trim(), password, displayName.trim() || undefined)
}
onLoginSuccess()
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : 'Okänt fel — försök igen.'
Alert.alert('Inloggning misslyckades', msg)
} finally {
setLoading(false)
}
}
return (
<KeyboardAvoidingView
style={styles.container}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
>
<ScrollView
contentContainerStyle={styles.inner}
keyboardShouldPersistTaps="handled"
showsVerticalScrollIndicator={false}
>
{/* Logo */}
<Text style={styles.logo}>quiXzoom</Text>
<Text style={styles.tagline}>Bli zoomer. Tjäna pengar. dina villkor.</Text>
{/* Mode toggle */}
<View style={styles.modeToggle}>
<TouchableOpacity
style={[styles.modeBtn, mode === 'login' && styles.modeBtnActive]}
onPress={() => setMode('login')}
>
<Text style={[styles.modeBtnText, mode === 'login' && styles.modeBtnTextActive]}>
Logga in
</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.modeBtn, mode === 'signup' && styles.modeBtnActive]}
onPress={() => setMode('signup')}
>
<Text style={[styles.modeBtnText, mode === 'signup' && styles.modeBtnTextActive]}>
Bli zoomer
</Text>
</TouchableOpacity>
</View>
{/* Form */}
<View style={styles.form}>
{mode === 'signup' && (
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>Namn (valfritt)</Text>
<TextInput
style={styles.input}
placeholder="Vad ska folk kalla dig?"
placeholderTextColor="rgba(255,255,255,0.25)"
value={displayName}
onChangeText={setDisplayName}
autoCapitalize="words"
returnKeyType="next"
/>
</View>
)}
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>E-post</Text>
<TextInput
style={styles.input}
placeholder="din@epost.se"
placeholderTextColor="rgba(255,255,255,0.25)"
value={email}
onChangeText={setEmail}
autoCapitalize="none"
keyboardType="email-address"
returnKeyType="next"
/>
</View>
<View style={styles.inputWrap}>
<Text style={styles.inputLabel}>Lösenord</Text>
<TextInput
style={styles.input}
placeholder={mode === 'signup' ? 'Minst 8 tecken' : '••••••••'}
placeholderTextColor="rgba(255,255,255,0.25)"
value={password}
onChangeText={setPassword}
secureTextEntry
returnKeyType="done"
onSubmitEditing={submit}
/>
</View>
<TouchableOpacity
style={[styles.btn, loading && styles.btnDisabled]}
onPress={submit}
disabled={loading}
>
<Text style={styles.btnText}>
{loading ? '…' : mode === 'login' ? 'Logga in' : 'Skapa zoomer-konto'}
</Text>
</TouchableOpacity>
</View>
{/* Sverige launch note */}
{mode === 'signup' && (
<View style={styles.launchNote}>
<Text style={styles.launchNoteText}>
🇸🇪 quiXzoom lanserar i Stockholms skärgård juni 2026.
Registrera dig nu och var bland de första zoomerarna.
</Text>
</View>
)}
</ScrollView>
</KeyboardAvoidingView>
)
}
const styles = StyleSheet.create({
container: { flex: 1, backgroundColor: '#0A0A1B' },
inner: { flexGrow: 1, justifyContent: 'center', padding: 24, paddingBottom: 48 },
logo: {
fontSize: 40, fontWeight: '900', color: '#fff',
textAlign: 'center', marginBottom: 6, letterSpacing: -2,
},
tagline: {
color: 'rgba(255,255,255,0.35)',
textAlign: 'center', marginBottom: 40, fontSize: 14, lineHeight: 20,
},
modeToggle: {
flexDirection: 'row',
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 14, padding: 4,
marginBottom: 28,
},
modeBtn: { flex: 1, paddingVertical: 10, alignItems: 'center', borderRadius: 10 },
modeBtnActive: { backgroundColor: '#6366f1' },
modeBtnText: { color: 'rgba(255,255,255,0.4)', fontWeight: '600', fontSize: 14 },
modeBtnTextActive: { color: '#fff' },
form: { gap: 14 },
inputWrap: { gap: 6 },
inputLabel: { color: 'rgba(255,255,255,0.45)', fontSize: 12, fontWeight: '600', letterSpacing: 0.5 },
input: {
backgroundColor: 'rgba(255,255,255,0.06)',
borderWidth: 1, borderColor: 'rgba(255,255,255,0.1)',
borderRadius: 14, paddingHorizontal: 16, paddingVertical: 14,
color: '#fff', fontSize: 16,
},
btn: {
backgroundColor: '#6366f1',
borderRadius: 14, paddingVertical: 16,
alignItems: 'center', marginTop: 4,
},
btnDisabled: { opacity: 0.6 },
btnText: { color: '#fff', fontWeight: '800', fontSize: 16 },
launchNote: {
marginTop: 24,
backgroundColor: 'rgba(99,102,241,0.1)',
borderRadius: 12, padding: 14,
borderWidth: 1, borderColor: 'rgba(99,102,241,0.25)',
},
launchNoteText: { color: 'rgba(255,255,255,0.55)', fontSize: 13, lineHeight: 20, textAlign: 'center' },
})
@@ -0,0 +1,259 @@
/**
* PasswordlessApprovalScreen — App visar godkännande för webb-inloggning
* Används när appen öppnas via deep link från webb
*/
import { useState, useEffect } from 'react'
import {
View,
Text,
TouchableOpacity,
StyleSheet,
ActivityIndicator,
Alert,
} from 'react-native'
import { auth } from '../../../lib/api'
interface Props {
sessionId: string
requestToken: string
onComplete: () => void
onCancel: () => void
}
export function PasswordlessApprovalScreen({
sessionId,
requestToken,
onComplete,
onCancel,
}: Props) {
const [loading, setLoading] = useState(false)
const [sessionInfo, setSessionInfo] = useState<{
deviceName?: string
location?: string
browser?: string
}>({})
useEffect(() => {
// Hämta session info om möjligt
fetchSessionInfo()
}, [])
const fetchSessionInfo = async () => {
try {
// TODO: Implementera endpoint för att hämta session info
// För nu visar vi generisk info
setSessionInfo({
deviceName: 'Webbbläsare',
location: 'Sverige',
browser: 'Chrome/Safari',
})
} catch (e) {
console.error('Kunde inte hämta session info:', e)
}
}
const handleApprove = async () => {
setLoading(true)
try {
// Skapa signatur (förenklad för MVP)
const timestamp = new Date().toISOString()
const signature = await createSignature(sessionId, requestToken, timestamp)
const res = await fetch('https://api.quixzoom.com/v1/auth/passwordless/approve', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getToken()}`,
},
body: JSON.stringify({
session_id: sessionId,
request_token: requestToken,
signature,
timestamp,
approving_device_id: await getDeviceId(),
}),
})
if (!res.ok) {
const err = await res.json()
throw new Error(err.detail || 'Kunde inte godkänna')
}
Alert.alert(
'✅ Inloggning godkänd',
'Du är nu inloggad på webben.',
[{ text: 'OK', onPress: onComplete }]
)
} catch (e: any) {
Alert.alert('Fel', e.message || 'Något gick fel')
} finally {
setLoading(false)
}
}
const handleReject = async () => {
try {
await fetch('https://api.quixzoom.com/v1/auth/passwordless/reject', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${await getToken()}`,
},
body: JSON.stringify({
session_id: sessionId,
request_token: requestToken,
device_id: await getDeviceId(),
}),
})
} catch (e) {
// Ignorera fel vid nekad
}
onCancel()
}
return (
<View style={styles.container}>
<View style={styles.card}>
<Text style={styles.icon}>🔐</Text>
<Text style={styles.title}>Inloggningsförfrågan</Text>
<Text style={styles.subtitle}>
Försöker du logga in quixzoom.se?
</Text>
<View style={styles.infoBox}>
<Text style={styles.infoText}>📱 Enhet: {sessionInfo.deviceName || 'Okänd'}</Text>
<Text style={styles.infoText}>🌐 Webbläsare: {sessionInfo.browser || 'Okänd'}</Text>
<Text style={styles.infoText}>📍 Plats: {sessionInfo.location || 'Okänd'}</Text>
</View>
<Text style={styles.warning}>
Godkänn endast om du själv försöker logga in.
</Text>
<View style={styles.buttonRow}>
<TouchableOpacity
style={[styles.button, styles.rejectButton]}
onPress={handleReject}
disabled={loading}
>
<Text style={styles.rejectButtonText}>Nej, neka</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.button, styles.approveButton]}
onPress={handleApprove}
disabled={loading}
>
{loading ? (
<ActivityIndicator color="#fff" />
) : (
<Text style={styles.approveButtonText}>Ja, godkänn</Text>
)}
</TouchableOpacity>
</View>
</View>
</View>
)
}
// Hjälpfunktioner
async function getToken(): Promise<string | null> {
// Använd samma token-hantering som i api.ts
const { getToken } = await import('../../../lib/api')
return getToken()
}
async function getDeviceId(): Promise<string> {
// TODO: Implementera enhets-ID-hantering
return 'device_unknown'
}
async function createSignature(
sessionId: string,
requestToken: string,
timestamp: string
): Promise<string> {
// TODO: Implementera riktig kryptografisk signatur
// För MVP använder vi en enkel hash
const data = `${sessionId}:${requestToken}:${timestamp}`
// I produktion: HMAC-SHA256 med enhets-privat-nyckel
return btoa(data)
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0A0A1B',
justifyContent: 'center',
padding: 24,
},
card: {
backgroundColor: 'rgba(255,255,255,0.05)',
borderRadius: 16,
padding: 32,
borderWidth: 1,
borderColor: 'rgba(255,255,255,0.1)',
},
icon: {
fontSize: 48,
textAlign: 'center',
marginBottom: 16,
},
title: {
fontSize: 24,
fontWeight: '800',
color: '#fff',
textAlign: 'center',
marginBottom: 8,
},
subtitle: {
fontSize: 16,
color: 'rgba(255,255,255,0.7)',
textAlign: 'center',
marginBottom: 24,
},
infoBox: {
backgroundColor: 'rgba(255,255,255,0.03)',
borderRadius: 12,
padding: 16,
marginBottom: 24,
},
infoText: {
fontSize: 14,
color: 'rgba(255,255,255,0.6)',
marginBottom: 8,
},
warning: {
fontSize: 13,
color: 'rgba(255,200,100,0.8)',
textAlign: 'center',
marginBottom: 24,
fontStyle: 'italic',
},
buttonRow: {
flexDirection: 'row',
gap: 12,
},
button: {
flex: 1,
padding: 16,
borderRadius: 12,
alignItems: 'center',
},
rejectButton: {
backgroundColor: 'rgba(255,255,255,0.1)',
},
rejectButtonText: {
color: 'rgba(255,255,255,0.8)',
fontSize: 16,
fontWeight: '600',
},
approveButton: {
backgroundColor: '#0066FF',
},
approveButtonText: {
color: '#fff',
fontSize: 16,
fontWeight: '700',
},
})
@@ -0,0 +1,7 @@
/**
* Auth features — exports all auth-related components and hooks
*/
export { AuthScreen } from './AuthScreen'
export { PasswordlessApprovalScreen } from './PasswordlessApprovalScreen'
export { usePasswordlessAuth } from './usePasswordlessAuth'
export type { AuthRequest } from './usePasswordlessAuth'
@@ -0,0 +1,178 @@
/**
* usePasswordlessAuth — hanterar push-notifieringar och deep links
* för passwordless cross-device authentication
*/
import { useState, useEffect, useCallback, useRef } from 'react'
import { Platform } from 'react-native'
import * as Notifications from 'expo-notifications'
import * as Linking from 'expo-linking'
import type { Subscription } from 'expo-notifications'
import type { EventType } from 'expo-linking'
import { passwordless } from '../../../lib/api'
export interface AuthRequest {
requestToken: string
deviceInfo?: {
name?: string
browser?: string
type?: string
}
}
export function usePasswordlessAuth(
onRequest: (request: AuthRequest) => void,
onApproved: () => void,
onDenied: () => void,
) {
const [pushToken, setPushToken] = useState<string | null>(null)
const notificationListener = useRef<Subscription | null>(null)
const responseListener = useRef<Subscription | null>(null)
const urlListener = useRef<{ remove: () => void } | null>(null)
// Register for push notifications
useEffect(() => {
registerForPushNotifications()
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current)
}
if (responseListener.current) {
Notifications.removeNotificationSubscription(responseListener.current)
}
if (urlListener.current) {
urlListener.current.remove()
}
}
}, [])
// Listen for incoming notifications
useEffect(() => {
// Foreground notification received
notificationListener.current = Notifications.addNotificationReceivedListener(
(notification: Notifications.Notification) => {
handleNotification(notification.request.content.data as Record<string, unknown>)
},
)
// Notification tapped
responseListener.current = Notifications.addNotificationResponseReceivedListener(
(response: Notifications.NotificationResponse) => {
handleNotification(response.notification.request.content.data as Record<string, unknown>)
},
)
// Deep links
urlListener.current = Linking.addEventListener('url', (event: EventType) => {
handleDeepLink(event.url)
})
// Check initial URL (app opened via deep link)
Linking.getInitialURL().then((url: string | null) => {
if (url) handleDeepLink(url)
})
return () => {
if (notificationListener.current) {
Notifications.removeNotificationSubscription(notificationListener.current)
}
if (responseListener.current) {
Notifications.removeNotificationSubscription(responseListener.current)
}
if (urlListener.current) {
urlListener.current.remove()
}
}
}, [onRequest, onApproved, onDenied])
const handleNotification = useCallback(
(data: Record<string, unknown> | undefined) => {
if (!data) return
if (data.type === 'passwordless_auth') {
const requestToken = data.request_token as string
const deviceInfo = data.device_info as AuthRequest['deviceInfo'] | undefined
if (requestToken) {
onRequest({ requestToken, deviceInfo })
}
}
if (data.type === 'passwordless_approved') {
onApproved()
}
if (data.type === 'passwordless_denied') {
onDenied()
}
},
[onRequest, onApproved, onDenied],
)
const handleDeepLink = useCallback(
(url: string) => {
const parsed = Linking.parse(url)
if (parsed.scheme === 'quixzoom' && parsed.path === 'auth/approve') {
const requestToken = parsed.queryParams?.token as string | undefined
const deviceName = parsed.queryParams?.device as string | undefined
if (requestToken) {
onRequest({
requestToken,
deviceInfo: {
name: deviceName || 'quixzoom.se',
type: 'web',
},
})
}
}
},
[onRequest],
)
const registerForPushNotifications = async () => {
try {
const { status: existingStatus } = await Notifications.getPermissionsAsync()
let finalStatus = existingStatus
if (existingStatus !== 'granted') {
const { status } = await Notifications.requestPermissionsAsync()
finalStatus = status
}
if (finalStatus !== 'granted') {
console.log('[usePasswordlessAuth] Push permission not granted')
return
}
const token = await Notifications.getExpoPushTokenAsync({
projectId: 'quixzoom',
})
setPushToken(token.data)
// Register push token with backend
await registerPushToken(token.data)
} catch (error) {
console.error('[usePasswordlessAuth] Failed to register push:', error)
}
}
const registerPushToken = async (token: string) => {
try {
await fetch('https://api.quixzoom.com/auth/push-token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
platform: Platform.OS,
}),
})
} catch (e) {
console.error('[usePasswordlessAuth] Failed to register push token:', e)
}
}
return { pushToken }
}
+5
View File
@@ -0,0 +1,5 @@
/**
* Hooks barrel export
*/
export { useLiveLocation } from './useLiveLocation'
export { useHeading } from './useHeading'
-107
View File
@@ -1,107 +0,0 @@
/**
* useCamera Hook
* React hook for camera operations
*/
import { useState, useEffect, useRef, useCallback } from 'react';
import { Camera, useCameraDevices } from 'react-native-vision-camera';
export function useCamera() {
const [hasPermission, setHasPermission] = useState(false);
const [isActive, setIsActive] = useState(true);
const [flashMode, setFlashMode] = useState('auto');
const [zoom, setZoom] = useState(0);
const [isCapturing, setIsCapturing] = useState(false);
const cameraRef = useRef(null);
const devices = useCameraDevices();
const device = devices.back;
// Check permission on mount
useEffect(() => {
checkPermission();
}, []);
const checkPermission = async () => {
const status = await Camera.requestCameraPermission();
setHasPermission(status === 'authorized');
};
/**
* Capture photo
*/
const capturePhoto = useCallback(async (options = {}) => {
if (!cameraRef.current || isCapturing) {
throw new Error('Camera not ready');
}
setIsCapturing(true);
try {
const photo = await cameraRef.current.takePhoto({
qualityPrioritization: options.quality || 'quality',
flash: options.flash || flashMode,
enableShutterSound: options.shutterSound !== false,
});
return {
path: photo.path,
width: photo.width,
height: photo.height,
timestamp: new Date().toISOString(),
};
} catch (error) {
console.error('Capture failed:', error);
throw error;
} finally {
setIsCapturing(false);
}
}, [flashMode, isCapturing]);
/**
* Toggle flash mode
*/
const toggleFlash = useCallback(() => {
setFlashMode(prev => {
const modes = ['off', 'auto', 'on'];
const currentIndex = modes.indexOf(prev);
return modes[(currentIndex + 1) % modes.length];
});
}, []);
/**
* Set zoom level
*/
const setZoomLevel = useCallback((level) => {
setZoom(Math.max(0, Math.min(1, level)));
}, []);
/**
* Focus on point
*/
const focus = useCallback(async (point) => {
if (cameraRef.current) {
await cameraRef.current.focus(point);
}
}, []);
return {
// Refs
cameraRef,
// State
hasPermission,
isActive,
flashMode,
zoom,
isCapturing,
device,
// Actions
capturePhoto,
toggleFlash,
setZoomLevel,
focus,
setIsActive,
};
}
+82
View File
@@ -0,0 +1,82 @@
/**
* useHeading.ts — React Hook for compass heading updates
* Uses watchHeadingAsync with headingFilter=1°
* Handles calibration state
*/
import { useEffect, useRef, useCallback } from 'react'
import * as Location from 'expo-location'
import { useLocationStore } from '../stores/locationStore'
const HEADING_FILTER = 1 // Update on every degree change
export function useHeading() {
const watchSubscription = useRef<Location.LocationSubscription | null>(null)
const { setHeading } = useLocationStore()
const startWatching = useCallback(async () => {
if (watchSubscription.current) {
console.log('[useHeading] Already watching, skipping start')
return
}
try {
// Check if compass/heading is available
const hasServices = await Location.hasServicesEnabledAsync()
if (!hasServices) {
console.log('[useHeading] Location services not enabled')
return
}
const { status } = await Location.requestForegroundPermissionsAsync()
if (status !== 'granted') {
console.log('[useHeading] Location permission denied')
return
}
watchSubscription.current = await Location.watchHeadingAsync(
(headingData) => {
// Use trueHeading if available, fallback to magneticHeading
const degrees = headingData.trueHeading >= 0
? headingData.trueHeading
: headingData.magHeading
const accuracy = headingData.accuracy
setHeading(degrees, accuracy)
// Log calibration state
if (headingData.accuracy < 0) {
console.log('[useHeading] Compass calibration needed')
}
}
)
console.log('[useHeading] watchHeadingAsync started with filter=1°')
} catch (error) {
console.error('[useHeading] Error starting heading watch:', error)
}
}, [setHeading])
const stopWatching = useCallback(() => {
if (watchSubscription.current) {
watchSubscription.current.remove()
watchSubscription.current = null
console.log('[useHeading] watchHeadingAsync stopped')
}
}, [])
// Auto-start on mount, stop on unmount
useEffect(() => {
startWatching()
return () => {
stopWatching()
}
}, [startWatching, stopWatching])
return {
startWatching,
stopWatching,
}
}
@@ -0,0 +1,132 @@
/**
* useLiveLocation.ts — React Hook for continuous GPS tracking
* Mission-grade config: BestForNavigation, distanceFilter=0, no pausing
* Includes stale-data rejection (reject positions older than 5s, accuracy > 20m)
*/
import { useEffect, useRef, useCallback } from 'react'
import * as Location from 'expo-location'
import { useLocationStore } from '../stores/locationStore'
const STALE_AGE_MS = 5000 // Reject positions older than 5 seconds
const MAX_ACCURACY_M = 20 // Reject positions with accuracy > 20m during mission
const MIN_ACCURACY_M = 150 // Reject positions with accuracy > 150m always
export function useLiveLocation() {
const watchSubscription = useRef<Location.LocationSubscription | null>(null)
const staleTimer = useRef<ReturnType<typeof setInterval> | null>(null)
const { isTracking, startTracking, stopTracking, updatePosition, markStale } = useLocationStore()
const validatePosition = useCallback((location: Location.LocationObject): boolean => {
const now = Date.now()
const age = now - location.timestamp
// Reject stale positions
if (age > STALE_AGE_MS) {
console.log(`[useLiveLocation] Rejected stale position: age=${age}ms > ${STALE_AGE_MS}ms`)
return false
}
// Reject low accuracy positions
const accuracy = location.coords.accuracy
if (accuracy == null) {
console.log('[useLiveLocation] Rejected: no accuracy data')
return false
}
if (accuracy > MIN_ACCURACY_M) {
console.log(`[useLiveLocation] Rejected poor accuracy: ${accuracy}m > ${MIN_ACCURACY_M}m`)
return false
}
// Warn if accuracy is mission-suboptimal but still acceptable
if (accuracy > MAX_ACCURACY_M) {
console.log(`[useLiveLocation] Warning: accuracy ${accuracy}m > mission-grade ${MAX_ACCURACY_M}m`)
// Still accept it — don't reject, just warn
}
return true
}, [])
const startWatching = useCallback(async () => {
if (watchSubscription.current) {
console.log('[useLiveLocation] Already watching, skipping start')
return
}
try {
// Request permissions
const { status: foregroundStatus } = await Location.requestForegroundPermissionsAsync()
if (foregroundStatus !== 'granted') {
console.log('[useLiveLocation] Foreground location permission denied')
useLocationStore.getState().setLocationAuthStatus('denied')
return
}
// Request background permission for iOS
if (foregroundStatus === 'granted') {
const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync()
useLocationStore.getState().setLocationAuthStatus(backgroundStatus)
}
// Mission-grade location config
const options: Location.LocationOptions = {
accuracy: Location.Accuracy.BestForNavigation,
distanceInterval: 0, // Update on every movement
timeInterval: 1000, // At least every 1 second
mayShowUserSettingsDialog: true,
}
watchSubscription.current = await Location.watchPositionAsync(
options,
(location) => {
if (validatePosition(location)) {
updatePosition(location)
}
}
)
startTracking()
console.log('[useLiveLocation] watchPositionAsync started with BestForNavigation')
// Start stale checker
staleTimer.current = setInterval(() => {
markStale()
}, 2000)
} catch (error) {
console.error('[useLiveLocation] Error starting location watch:', error)
useLocationStore.getState().setLocationAuthStatus('error')
}
}, [validatePosition, startTracking, updatePosition, markStale])
const stopWatching = useCallback(() => {
if (watchSubscription.current) {
watchSubscription.current.remove()
watchSubscription.current = null
console.log('[useLiveLocation] watchPositionAsync stopped')
}
if (staleTimer.current) {
clearInterval(staleTimer.current)
staleTimer.current = null
}
stopTracking()
}, [stopTracking])
// Auto-start on mount, stop on unmount
useEffect(() => {
startWatching()
return () => {
stopWatching()
}
}, [startWatching, stopWatching])
return {
isTracking,
startWatching,
stopWatching,
}
}
-147
View File
@@ -1,147 +0,0 @@
/**
* useLocation Hook
* React hook for GPS location tracking
*/
import { useState, useEffect, useCallback, useRef } from 'react';
import Geolocation from '@react-native-community/geolocation';
export function useLocation(options = {}) {
const [location, setLocation] = useState(null);
const [hasPermission, setHasPermission] = useState(false);
const [isTracking, setIsTracking] = useState(false);
const [error, setError] = useState(null);
const watchIdRef = useRef(null);
const defaultOptions = {
enableHighAccuracy: true,
timeout: 15000,
maximumAge: 10000,
distanceFilter: 10, // meters
...options,
};
/**
* Request location permission
*/
const requestPermission = useCallback(async () => {
try {
const result = await Geolocation.requestAuthorization();
setHasPermission(result === 'granted');
return result === 'granted';
} catch (err) {
setError(err.message);
return false;
}
}, []);
/**
* Get current location (one-time)
*/
const getCurrentLocation = useCallback(() => {
return new Promise((resolve, reject) => {
Geolocation.getCurrentPosition(
(position) => {
const loc = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
altitude: position.coords.altitude,
accuracy: position.coords.accuracy,
heading: position.coords.heading,
speed: position.coords.speed,
timestamp: new Date(position.timestamp).toISOString(),
};
setLocation(loc);
setError(null);
resolve(loc);
},
(err) => {
setError(err.message);
reject(err);
},
defaultOptions
);
});
}, [defaultOptions]);
/**
* Start continuous location tracking
*/
const startTracking = useCallback(() => {
if (watchIdRef.current) {
return; // Already tracking
}
setIsTracking(true);
watchIdRef.current = Geolocation.watchPosition(
(position) => {
const loc = {
latitude: position.coords.latitude,
longitude: position.coords.longitude,
altitude: position.coords.altitude,
accuracy: position.coords.accuracy,
heading: position.coords.heading,
speed: position.coords.speed,
timestamp: new Date(position.timestamp).toISOString(),
};
setLocation(loc);
setError(null);
},
(err) => {
setError(err.message);
},
{
...defaultOptions,
distanceFilter: defaultOptions.distanceFilter,
}
);
}, [defaultOptions]);
/**
* Stop location tracking
*/
const stopTracking = useCallback(() => {
if (watchIdRef.current) {
Geolocation.clearWatch(watchIdRef.current);
watchIdRef.current = null;
}
setIsTracking(false);
}, []);
/**
* Toggle tracking
*/
const toggleTracking = useCallback(() => {
if (isTracking) {
stopTracking();
} else {
startTracking();
}
}, [isTracking, startTracking, stopTracking]);
// Cleanup on unmount
useEffect(() => {
return () => {
if (watchIdRef.current) {
Geolocation.clearWatch(watchIdRef.current);
}
};
}, []);
return {
// State
location,
hasPermission,
isTracking,
error,
// Actions
requestPermission,
getCurrentLocation,
startTracking,
stopTracking,
toggleTracking,
};
}
-175
View File
@@ -1,175 +0,0 @@
/"/**
* useMissions Hook
* React hook for mission management
*/
import { useState, useEffect, useCallback } from 'react';
// Mock data for development
const MOCK_MISSIONS = [
{
id: 'M001',
title: 'Street Light Inspection',
description: 'Document condition of street lights in downtown area',
category: 'infrastructure',
difficulty: 'easy',
reward_usd: 25,
total_observations: 10,
completed_observations: 3,
distance: 1.2,
deadline: '2026-07-01',
status: 'active',
coordinates: { lat: 59.3293, lng: 18.0686 },
},
{
id: 'M002',
title: 'Bridge Safety Check',
description: 'Visual inspection of pedestrian bridge for defects',
category: 'bridge',
difficulty: 'medium',
reward_usd: 75,
total_observations: 5,
completed_observations: 0,
distance: 3.5,
deadline: '2026-07-05',
status: 'active',
coordinates: { lat: 59.3301, lng: 18.0692 },
},
{
id: 'M003',
title: 'Building Facade Survey',
description: 'Photograph and assess building facade condition',
category: 'building',
difficulty: 'hard',
reward_usd: 150,
total_observations: 20,
completed_observations: 8,
distance: 0.8,
deadline: '2026-07-10',
status: 'active',
coordinates: { lat: 59.3289, lng: 18.0678 },
},
];
export function useMissions(apiClient) {
const [missions, setMissions] = useState([]);
const [activeMission, setActiveMission] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
/**
* Load missions from API
*/
const loadMissions = useCallback(async () => {
setLoading(true);
setError(null);
try {
// In production: const data = await apiClient.missions.list();
// For now, use mock data
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate API call
setMissions(MOCK_MISSIONS);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}, [apiClient]);
/**
* Accept a mission
*/
const acceptMission = useCallback(async (missionId) => {
try {
// In production: await apiClient.missions.accept(missionId);
setMissions(prev =>
prev.map(m =>
m.id === missionId ? { ...m, status: 'accepted' } : m
)
);
return true;
} catch (err) {
setError(err.message);
return false;
}
}, [apiClient]);
/**
* Complete a mission
*/
const completeMission = useCallback(async (missionId, observations) => {
try {
// In production: await apiClient.missions.complete(missionId, { observations });
setMissions(prev =>
prev.map(m =>
m.id === missionId
? { ...m, status: 'completed', completed_observations: m.total_observations }
: m
)
);
return true;
} catch (err) {
setError(err.message);
return false;
}
}, [apiClient]);
/**
* Select active mission
*/
const selectMission = useCallback((mission) => {
setActiveMission(prev => (prev?.id === mission?.id ? null : mission));
}, []);
/**
* Get missions by status
*/
const getMissionsByStatus = useCallback((status) => {
return missions.filter(m => m.status === status);
}, [missions]);
/**
* Get nearby missions
*/
const getNearbyMissions = useCallback((lat, lng, radius = 10) => {
// Simple distance calculation (km)
const distance = (lat1, lng1, lat2, lng2) => {
const R = 6371;
const dLat = (lat2 - lat1) * Math.PI / 180;
const dLng = (lng2 - lng1) * Math.PI / 180;
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
Math.sin(dLng / 2) * Math.sin(dLng / 2);
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
};
return missions
.map(m => ({
...m,
calculatedDistance: distance(lat, lng, m.coordinates.lat, m.coordinates.lng),
}))
.filter(m => m.calculatedDistance <= radius)
.sort((a, b) => a.calculatedDistance - b.calculatedDistance);
}, [missions]);
// Load missions on mount
useEffect(() => {
loadMissions();
}, [loadMissions]);
return {
// State
missions,
activeMission,
loading,
error,
// Actions
loadMissions,
acceptMission,
completeMission,
selectMission,
getMissionsByStatus,
getNearbyMissions,
};
}
@@ -1,90 +0,0 @@
/**
* AppNavigator
* Root navigation with tab and stack navigators
*/
import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { createStackNavigator } from '@react-navigation/stack';
import Icon from 'react-native-vector-icons/MaterialIcons';
// Screens
import HomeScreen from '../screens/HomeScreen';
import StatsScreen from '../screens/StatsScreen';
import ProfileScreen from '../screens/ProfileScreen';
const Tab = createBottomTabNavigator();
const Stack = createStackNavigator();
// Home Stack
function HomeStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="HomeMain" component={HomeScreen} />
</Stack.Navigator>
);
}
// Main Tab Navigator
function MainTabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
headerShown: false,
tabBarStyle: {
backgroundColor: '#1C1C1E',
borderTopColor: '#2C2C2E',
paddingBottom: 8,
paddingTop: 8,
},
tabBarActiveTintColor: '#007AFF',
tabBarInactiveTintColor: '#8E8E93',
tabBarIcon: ({ focused, color, size }) => {
let iconName;
switch (route.name) {
case 'Home':
iconName = 'camera-alt';
break;
case 'Stats':
iconName = 'bar-chart';
break;
case 'Profile':
iconName = 'person';
break;
default:
iconName = 'circle';
}
return <Icon name={iconName} size={size} color={color} />;
},
})}
>
<Tab.Screen
name="Home"
component={HomeStack}
options={{ tabBarLabel: 'Capture' }}
/>
<Tab.Screen
name="Stats"
component={StatsScreen}
options={{ tabBarLabel: 'Stats' }}
/>
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{ tabBarLabel: 'Profile' }}
/>
</Tab.Navigator>
);
}
// Root Navigator
export default function AppNavigator() {
return (
<NavigationContainer>
<MainTabs />
</NavigationContainer>
);
}
@@ -0,0 +1,244 @@
/**
* AppNavigator — Root navigation with location permission request at startup
* Starts location tracking when user is authenticated
* Includes passwordless cross-device auth support
*/
import { useEffect, useState, useCallback } from 'react'
import { NavigationContainer } from '@react-navigation/native'
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'
import { createNativeStackNavigator } from '@react-navigation/native-stack'
import { Text, View, ActivityIndicator, Alert } from 'react-native'
import * as Location from 'expo-location'
import * as Notifications from 'expo-notifications'
import { getToken, auth } from '../../lib/api'
import { HomeScreen } from '../../screens/HomeScreen'
import { MissionsScreen } from '../../screens/MissionsScreen'
import { MissionDetailScreen } from '../../screens/MissionDetailScreen'
import { EarningsScreen } from '../../screens/EarningsScreen'
import { ProfileScreen } from '../../screens/ProfileScreen'
import { AuthScreen } from '../features/auth/AuthScreen'
import { PasswordlessApprovalScreen } from '../features/auth/PasswordlessApprovalScreen'
import { usePasswordlessAuth } from '../features/auth/usePasswordlessAuth'
import { useLocationStore } from '../stores/locationStore'
const Tab = createBottomTabNavigator()
const Stack = createNativeStackNavigator()
function MissionsStack() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="MissionsList" component={MissionsScreen} />
<Stack.Screen name="MissionDetail" component={MissionDetailScreen} />
</Stack.Navigator>
)
}
function MainTabs() {
return (
<Tab.Navigator
screenOptions={{
tabBarStyle: {
backgroundColor: '#0A0A1B',
borderTopColor: 'rgba(255,255,255,0.08)',
},
tabBarActiveTintColor: '#6366f1',
tabBarInactiveTintColor: 'rgba(255,255,255,0.35)',
headerShown: false,
}}
>
<Tab.Screen
name="Karta"
component={HomeScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>🗺</Text>,
}}
/>
<Tab.Screen
name="Uppdrag"
component={MissionsStack}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>🎯</Text>,
}}
/>
<Tab.Screen
name="Intjänat"
component={EarningsScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>💰</Text>,
}}
/>
<Tab.Screen
name="Profil"
component={ProfileScreen}
options={{
tabBarIcon: ({ color }) => <Text style={{ fontSize: 20, color }}>👤</Text>,
}}
/>
</Tab.Navigator>
)
}
// Configure notification handler
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
})
export function AppNavigator() {
const [isLoggedIn, setIsLoggedIn] = useState<boolean | null>(null)
const [authRequest, setAuthRequest] = useState<{
requestToken: string
deviceInfo?: { name?: string; browser?: string; type?: string }
} | null>(null)
const { setLocationAuthStatus } = useLocationStore()
const handleAuthRequest = useCallback(
(request: { requestToken: string; deviceInfo?: { name?: string; browser?: string; type?: string } }) => {
setAuthRequest(request)
},
[],
)
const handleApproved = useCallback(() => {
setAuthRequest(null)
Alert.alert('✅ Inloggning godkänd', 'Användaren kan nu logga in på webben.')
}, [])
const handleDenied = useCallback(() => {
setAuthRequest(null)
Alert.alert('❌ Inloggning nekad', 'Förfrågan avvisad.')
}, [])
const handleClose = useCallback(() => {
setAuthRequest(null)
}, [])
// Initialize passwordless auth hooks
usePasswordlessAuth(handleAuthRequest, handleApproved, handleDenied)
useEffect(() => {
checkAuth()
requestLocationPermissions()
configureNotifications()
}, [])
const checkAuth = async () => {
const token = await getToken()
if (token) {
// Validate token by calling /auth/me
try {
await auth.me()
setIsLoggedIn(true)
} catch {
setIsLoggedIn(false)
}
} else {
setIsLoggedIn(false)
}
}
const requestLocationPermissions = async () => {
try {
// Check if location services are enabled
const servicesEnabled = await Location.hasServicesEnabledAsync()
if (!servicesEnabled) {
console.log('[AppNavigator] Location services not enabled')
setLocationAuthStatus('services_disabled')
return
}
// Request foreground permission
const { status: foregroundStatus } = await Location.requestForegroundPermissionsAsync()
console.log(`[AppNavigator] Foreground permission: ${foregroundStatus}`)
setLocationAuthStatus(foregroundStatus)
if (foregroundStatus !== 'granted') {
Alert.alert(
'GPS-behörighet krävs',
'quiXzoom behöver åtkomst till din plats för att visa uppdrag nära dig och navigera till uppdragsplatser.',
[
{ text: 'OK', style: 'default' },
]
)
return
}
// Request background permission (iOS)
const { status: backgroundStatus } = await Location.requestBackgroundPermissionsAsync()
console.log(`[AppNavigator] Background permission: ${backgroundStatus}`)
if (backgroundStatus === 'granted') {
setLocationAuthStatus('granted')
}
} catch (error) {
console.error('[AppNavigator] Error requesting location permissions:', error)
setLocationAuthStatus('error')
}
}
const configureNotifications = async () => {
try {
// Set notification categories for auth requests
await Notifications.setNotificationCategoryAsync('passwordless_auth', [
{
identifier: 'approve',
buttonTitle: 'Godkänn',
options: { isDestructive: false },
},
{
identifier: 'deny',
buttonTitle: 'Neka',
options: { isDestructive: true },
},
])
} catch (error) {
console.error('[AppNavigator] Error configuring notifications:', error)
}
}
if (isLoggedIn === null) {
return (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#0A0A1B' }}>
<ActivityIndicator color="#6366f1" size="large" />
</View>
)
}
if (!isLoggedIn) {
return (
<View style={{ flex: 1 }}>
<AuthScreen onLoginSuccess={() => setIsLoggedIn(true)} />
{authRequest && (
<PasswordlessApprovalScreen
requestToken={authRequest.requestToken}
deviceInfo={authRequest.deviceInfo}
onApproved={handleApproved}
onDenied={handleDenied}
onClose={handleClose}
/>
)}
</View>
)
}
return (
<NavigationContainer>
<View style={{ flex: 1 }}>
<MainTabs />
{authRequest && (
<PasswordlessApprovalScreen
requestToken={authRequest.requestToken}
deviceInfo={authRequest.deviceInfo}
onApproved={handleApproved}
onDenied={handleDenied}
onClose={handleClose}
/>
)}
</View>
</NavigationContainer>
)
}
-287
View File
@@ -1,287 +0,0 @@
/**
* HomeScreen
* Main screen with camera, missions, and quick actions
*/
import React, { useState, useEffect } from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
RefreshControl,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import CameraView from '../components/CameraView';
import MissionCard from '../components/MissionCard';
import EvidencePanel from '../components/EvidencePanel';
export default function HomeScreen({ navigation }) {
const [activeTab, setActiveTab] = useState('camera'); // camera | missions | evidence
const [missions, setMissions] = useState([]);
const [activeMission, setActiveMission] = useState(null);
const [capturedImage, setCapturedImage] = useState(null);
const [evidence, setEvidence] = useState(null);
const [isRefreshing, setIsRefreshing] = useState(false);
// Load missions
useEffect(() => {
loadMissions();
}, []);
const loadMissions = async () => {
// Mock data - replace with API call
setMissions([
{
id: 'M001',
title: 'Street Light Inspection',
description: 'Document condition of street lights in downtown area',
category: 'infrastructure',
difficulty: 'easy',
reward_usd: 25,
total_observations: 10,
completed_observations: 3,
distance: 1.2,
deadline: '2026-07-01',
},
{
id: 'M002',
title: 'Bridge Safety Check',
description: 'Visual inspection of pedestrian bridge for defects',
category: 'bridge',
difficulty: 'medium',
reward_usd: 75,
total_observations: 5,
completed_observations: 0,
distance: 3.5,
deadline: '2026-07-05',
},
{
id: 'M003',
title: 'Building Facade Survey',
description: 'Photograph and assess building facade condition',
category: 'building',
difficulty: 'hard',
reward_usd: 150,
total_observations: 20,
completed_observations: 8,
distance: 0.8,
deadline: '2026-07-10',
},
]);
};
// Handle photo capture
const handleCapture = (photo) => {
setCapturedImage(photo);
setActiveTab('evidence');
// Mock evidence extraction
setEvidence({
metadata: {
latitude: 59.3293,
longitude: 18.0686,
accuracy: 5,
device_model: 'iPhone14,2',
},
visual_objects: [
{ label: 'street_light', confidence: 0.95 },
{ label: 'road', confidence: 0.88 },
],
semantic_objects: [
{ label: 'urban_street', confidence: 0.92 },
],
text_detections: [],
geometric_features: {
horizon_detected: true,
vanishing_points: 2,
},
environmental_signals: {
time_of_day: 'day',
lighting_quality: 'good',
weather: 'clear',
},
temporal_signals: {
season: 'summer',
day_of_week: 'Friday',
},
});
};
// Handle mission selection
const handleMissionPress = (mission) => {
setActiveMission(activeMission?.id === mission.id ? null : mission);
};
// Handle refresh
const handleRefresh = async () => {
setIsRefreshing(true);
await loadMissions();
setIsRefreshing(false);
};
// Render camera tab
const renderCamera = () => (
<CameraView
onCapture={handleCapture}
mission={activeMission}
evidence={evidence}
/>
);
// Render missions tab
const renderMissions = () => (
<ScrollView
style={styles.missionsContainer}
refreshControl={
<RefreshControl refreshing={isRefreshing} onRefresh={handleRefresh} />
}
>
<Text style={styles.sectionTitle}>Available Missions</Text>
{missions.map((mission) => (
<MissionCard
key={mission.id}
mission={mission}
onPress={handleMissionPress}
isActive={activeMission?.id === mission.id}
/>
))}
</ScrollView>
);
// Render evidence tab
const renderEvidence = () => (
<EvidencePanel
evidence={evidence}
confidence={0.85}
/>
);
return (
<View style={styles.container}>
{/* Content */}
<View style={styles.content}>
{activeTab === 'camera' && renderCamera()}
{activeTab === 'missions' && renderMissions()}
{activeTab === 'evidence' && renderEvidence()}
</View>
{/* Bottom Navigation */}
<View style={styles.bottomNav}>
<TouchableOpacity
style={[styles.navButton, activeTab === 'camera' && styles.navButtonActive]}
onPress={() => setActiveTab('camera')}
>
<Icon name="camera-alt" size={24} color={activeTab === 'camera' ? '#007AFF' : '#8E8E93'} />
<Text style={[styles.navText, activeTab === 'camera' && styles.navTextActive]}>Capture</Text>
</TouchableOpacity>
<TouchableOpacity
style={[styles.navButton, activeTab === 'missions' && styles.navButtonActive]}
onPress={() => setActiveTab('missions')}
>
<Icon name="assignment" size={24} color={activeTab === 'missions' ? '#007AFF' : '#8E8E93'} />
<Text style={[styles.navText, activeTab === 'missions' && styles.navTextActive]}>Missions</Text>
{missions.length > 0 && (
<View style={styles.badge}>
<Text style={styles.badgeText}>{missions.length}</Text>
</View>
)}
</TouchableOpacity>
<TouchableOpacity
style={[styles.navButton, activeTab === 'evidence' && styles.navButtonActive]}
onPress={() => setActiveTab('evidence')}
>
<Icon name="search" size={24} color={activeTab === 'evidence' ? '#007AFF' : '#8E8E93'} />
<Text style={[styles.navText, activeTab === 'evidence' && styles.navTextActive]}>Evidence</Text>
{evidence && (
<View style={[styles.badge, styles.badgeActive]}>
<Text style={styles.badgeText}>1</Text>
</View>
)}
</TouchableOpacity>
<TouchableOpacity
style={styles.navButton}
onPress={() => navigation.navigate('Stats')}
>
<Icon name="bar-chart" size={24} color="#8E8E93" />
<Text style={styles.navText}>Stats</Text>
</TouchableOpacity>
<TouchableOpacity
style={styles.navButton}
onPress={() => navigation.navigate('Profile')}
>
<Icon name="person" size={24} color="#8E8E93" />
<Text style={styles.navText}>Profile</Text>
</TouchableOpacity>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
content: {
flex: 1,
},
missionsContainer: {
flex: 1,
padding: 16,
},
sectionTitle: {
fontSize: 24,
fontWeight: '700',
color: '#fff',
marginBottom: 16,
},
bottomNav: {
flexDirection: 'row',
justifyContent: 'space-around',
paddingVertical: 12,
backgroundColor: '#1C1C1E',
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
},
navButton: {
alignItems: 'center',
position: 'relative',
},
navButtonActive: {
// Active state styling
},
navText: {
fontSize: 10,
color: '#8E8E93',
marginTop: 4,
},
navTextActive: {
color: '#007AFF',
},
badge: {
position: 'absolute',
top: -4,
right: -8,
backgroundColor: '#FF3B30',
borderRadius: 10,
minWidth: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
},
badgeActive: {
backgroundColor: '#34C759',
},
badgeText: {
color: '#fff',
fontSize: 12,
fontWeight: '700',
paddingHorizontal: 4,
},
});
@@ -1,250 +0,0 @@
/**
* ProfileScreen
* User profile, settings, and account management
*/
import React from 'react';
import {
View,
Text,
StyleSheet,
TouchableOpacity,
ScrollView,
Switch,
} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
export default function ProfileScreen({ navigation }) {
const [notifications, setNotifications] = React.useState(true);
const [offlineMode, setOfflineMode] = React.useState(false);
const [autoSync, setAutoSync] = React.useState(true);
const renderSettingItem = (icon, title, subtitle, value, onValueChange) => (
<View style={styles.settingItem}>
<View style={styles.settingIcon}>
<Icon name={icon} size={20} color="#007AFF" />
</View>
<View style={styles.settingContent}>
<Text style={styles.settingTitle}>{title}</Text>
{subtitle && <Text style={styles.settingSubtitle}>{subtitle}</Text>}
</View>
{value !== undefined && (
<Switch
value={value}
onValueChange={onValueChange}
trackColor={{ false: '#2C2C2E', true: '#34C759' }}
thumbColor="#fff"
/>
)}
</View>
);
const renderActionItem = (icon, title, color = '#007AFF', onPress) => (
<TouchableOpacity style={styles.actionItem} onPress={onPress}>
<View style={[styles.actionIcon, { backgroundColor: `${color}20` }]}>
<Icon name={icon} size={20} color={color} />
</View>
<Text style={[styles.actionText, { color }]}>{title}</Text>
<Icon name="chevron-right" size={20} color="#8E8E93" />
</TouchableOpacity>
);
return (
<ScrollView style={styles.container}>
{/* Profile Header */}
<View style={styles.header}>
<View style={styles.avatar}>
<Text style={styles.avatarText}>ES</Text>
</View>
<Text style={styles.name}>Erik Svensson</Text>
<Text style={styles.email}>erik@landvex.com</Text>
<View style={styles.levelBadge}>
<Text style={styles.levelText}>Active Zoomer</Text>
</View>
</View>
{/* Stats Summary */}
<View style={styles.statsRow}>
<View style={styles.statItem}>
<Text style={styles.statValue}>156</Text>
<Text style={styles.statLabel}>Observations</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statValue}>$1,250</Text>
<Text style={styles.statLabel}>Earned</Text>
</View>
<View style={styles.statDivider} />
<View style={styles.statItem}>
<Text style={styles.statValue}>4.8</Text>
<Text style={styles.statLabel}>Rating</Text>
</View>
</View>
{/* Settings */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Settings</Text>
{renderSettingItem('notifications', 'Push Notifications', 'Mission alerts and updates', notifications, setNotifications)}
{renderSettingItem('cloud-off', 'Offline Mode', 'Work without internet', offlineMode, setOfflineMode)}
{renderSettingItem('sync', 'Auto Sync', 'Upload when connected', autoSync, setAutoSync)}
</View>
{/* Account */}
<View style={styles.section}>
<Text style={styles.sectionTitle}>Account</Text>
{renderActionItem('payment', 'Payment Methods', '#007AFF')}
{renderActionItem('history', 'Transaction History', '#007AFF')}
{renderActionItem('verified-user', 'Verification Status', '#34C759')}
{renderActionItem('help', 'Help & Support', '#007AFF')}
</View>
{/* Danger Zone */}
<View style={styles.section}>
{renderActionItem('logout', 'Sign Out', '#FF3B30')}
</View>
{/* Version */}
<Text style={styles.version}>quiXzoom v1.0.0</Text>
</ScrollView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
header: {
alignItems: 'center',
padding: 24,
backgroundColor: '#1C1C1E',
},
avatar: {
width: 80,
height: 80,
borderRadius: 40,
backgroundColor: '#007AFF',
justifyContent: 'center',
alignItems: 'center',
marginBottom: 12,
},
avatarText: {
fontSize: 28,
fontWeight: '700',
color: '#fff',
},
name: {
fontSize: 20,
fontWeight: '600',
color: '#fff',
},
email: {
fontSize: 14,
color: '#8E8E93',
marginTop: 4,
},
levelBadge: {
backgroundColor: 'rgba(0,122,255,0.2)',
paddingHorizontal: 12,
paddingVertical: 4,
borderRadius: 12,
marginTop: 8,
},
levelText: {
color: '#007AFF',
fontSize: 12,
fontWeight: '600',
},
statsRow: {
flexDirection: 'row',
justifyContent: 'space-around',
padding: 20,
backgroundColor: '#1C1C1E',
marginTop: 1,
},
statItem: {
alignItems: 'center',
},
statValue: {
fontSize: 20,
fontWeight: '700',
color: '#fff',
},
statLabel: {
fontSize: 12,
color: '#8E8E93',
marginTop: 4,
},
statDivider: {
width: 1,
backgroundColor: '#2C2C2E',
},
section: {
marginTop: 16,
backgroundColor: '#1C1C1E',
borderRadius: 12,
marginHorizontal: 16,
overflow: 'hidden',
},
sectionTitle: {
fontSize: 13,
fontWeight: '600',
color: '#8E8E93',
textTransform: 'uppercase',
padding: 16,
paddingBottom: 8,
},
settingItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
},
settingIcon: {
width: 32,
height: 32,
borderRadius: 8,
backgroundColor: 'rgba(0,122,255,0.1)',
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
settingContent: {
flex: 1,
},
settingTitle: {
fontSize: 16,
color: '#fff',
},
settingSubtitle: {
fontSize: 12,
color: '#8E8E93',
marginTop: 2,
},
actionItem: {
flexDirection: 'row',
alignItems: 'center',
padding: 16,
borderTopWidth: 1,
borderTopColor: '#2C2C2E',
},
actionIcon: {
width: 32,
height: 32,
borderRadius: 8,
justifyContent: 'center',
alignItems: 'center',
marginRight: 12,
},
actionText: {
flex: 1,
fontSize: 16,
},
version: {
textAlign: 'center',
color: '#8E8E93',
fontSize: 12,
padding: 24,
},
});
@@ -1,70 +0,0 @@
/**
* StatsScreen
* Zoomer statistics and earnings
*/
import React from 'react';
import { View, StyleSheet } from 'react-native';
import StatsView from '../components/StatsView';
export default function StatsScreen() {
// Mock stats - replace with API call
const stats = {
total_earnings: 1250.00,
earnings_growth: 23.5,
total_observations: 156,
approved_observations: 148,
completed_missions: 12,
active_missions: 3,
rating: 4.8,
reviews: 24,
avg_response_time: 4.2,
level: 'Active',
level_progress: 65,
countries: 2,
approval_rate: 94.9,
recent_activity: [
{
text: 'Completed Street Light Inspection',
time: '2 hours ago',
amount: 25.00,
icon: 'check-circle',
color: '#34C759',
},
{
text: 'New mission available: Bridge Safety',
time: '5 hours ago',
amount: null,
icon: 'new-releases',
color: '#007AFF',
},
{
text: 'Observation approved',
time: '1 day ago',
amount: 15.00,
icon: 'thumb-up',
color: '#34C759',
},
{
text: 'Bonus: Early completion',
time: '2 days ago',
amount: 10.00,
icon: 'star',
color: '#FF9500',
},
],
};
return (
<View style={styles.container}>
<StatsView stats={stats} />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#000',
},
});
@@ -0,0 +1,8 @@
/**
* LEGACY: This file previously contained the Supabase client.
* Auth has been migrated to identity-core JWT via lib/api.ts
*
* Re-exports from lib/api.ts for backwards compatibility.
* Remove this file once all src/features/* screens have been updated.
*/
export { getToken, setToken, clearTokens, auth } from '../../lib/api'
-177
View File
@@ -1,177 +0,0 @@
//**
* Redux Store
* Global state management for quiXzoom app
*/
import { configureStore, createSlice } from '@reduxjs/toolkit';
import { persistStore, persistReducer } from 'redux-persist';
import AsyncStorage from '@react-native-async-storage/async-storage';
// Auth slice
const authSlice = createSlice({
name: 'auth',
initialState: {
token: null,
user: null,
isAuthenticated: false,
},
reducers: {
setToken: (state, action) => {
state.token = action.payload;
state.isAuthenticated = !!action.payload;
},
setUser: (state, action) => {
state.user = action.payload;
},
logout: (state) => {
state.token = null;
state.user = null;
state.isAuthenticated = false;
},
},
});
// Mission slice
const missionSlice = createSlice({
name: 'missions',
initialState: {
list: [],
active: null,
loading: false,
error: null,
},
reducers: {
setMissions: (state, action) => {
state.list = action.payload;
},
setActiveMission: (state, action) => {
state.active = action.payload;
},
addMission: (state, action) => {
state.list.push(action.payload);
},
updateMission: (state, action) => {
const index = state.list.findIndex(m => m.id === action.payload.id);
if (index !== -1) {
state.list[index] = action.payload;
}
},
setLoading: (state, action) => {
state.loading = action.payload;
},
setError: (state, action) => {
state.error = action.payload;
},
},
});
// Observation slice
const observationSlice = createSlice({
name: 'observations',
initialState: {
queue: [],
uploaded: [],
current: null,
evidence: null,
},
reducers: {
addToQueue: (state, action) => {
state.queue.push(action.payload);
},
removeFromQueue: (state, action) => {
state.queue = state.queue.filter(o => o.id !== action.payload);
},
setCurrentObservation: (state, action) => {
state.current = action.payload;
},
setEvidence: (state, action) => {
state.evidence = action.payload;
},
markUploaded: (state, action) => {
state.uploaded.push(action.payload);
state.queue = state.queue.filter(o => o.id !== action.payload.id);
},
clearQueue: (state) => {
state.queue = [];
},
},
});
// Stats slice
const statsSlice = createSlice({
name: 'stats',
initialState: {
earnings: 0,
observations: 0,
missions: 0,
rating: 0,
level: 'Supplementary',
loading: false,
},
reducers: {
setStats: (state, action) => {
return { ...state, ...action.payload };
},
addEarnings: (state, action) => {
state.earnings += action.payload;
},
incrementObservations: (state) => {
state.observations += 1;
},
setLoading: (state, action) => {
state.loading = action.payload;
},
},
});
// Network slice
const networkSlice = createSlice({
name: 'network',
initialState: {
isConnected: true,
isInternetReachable: true,
},
reducers: {
setConnectionStatus: (state, action) => {
state.isConnected = action.payload.isConnected;
state.isInternetReachable = action.payload.isInternetReachable;
},
},
});
// Export actions
export const { setToken, setUser, logout } = authSlice.actions;
export const { setMissions, setActiveMission, addMission, updateMission } = missionSlice.actions;
export const { addToQueue, removeFromQueue, setCurrentObservation, setEvidence, markUploaded, clearQueue } = observationSlice.actions;
export const { setStats, addEarnings, incrementObservations } = statsSlice.actions;
export const { setConnectionStatus } = networkSlice.actions;
// Persist config
const persistConfig = {
key: 'root',
storage: AsyncStorage,
whitelist: ['auth', 'missions', 'stats'],
};
// Root reducer
const rootReducer = {
auth: authSlice.reducer,
missions: missionSlice.reducer,
observations: observationSlice.reducer,
stats: statsSlice.reducer,
network: networkSlice.reducer,
};
// Store
export const store = configureStore({
reducer: rootReducer,
middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({
serializableCheck: {
ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'],
},
}),
});
// Persistor
export const persistor = persistStore(store);
+5
View File
@@ -0,0 +1,5 @@
/**
* Stores barrel export
*/
export { useLocationStore } from './locationStore'
export type { LocationState } from './locationStore'
@@ -0,0 +1,142 @@
/**
* locationStore.ts — Zustand store for live GPS location
* Holds current position, heading, accuracy, speed, tracking state
*/
import { create } from 'zustand'
import type { LocationObject } from 'expo-location'
export interface LocationState {
// Position data
currentPosition: LocationObject | null
heading: number | null
headingAccuracy: number | null
accuracy: number | null
speed: number | null
course: number | null
// Tracking state
isTracking: boolean
isStale: boolean
lastUpdateTimestamp: number | null
updateIntervalMs: number | null
// Camera / UI state
followUser: boolean
userPannedMap: boolean
// Auth status
locationAuthStatus: string | null
// Actions
startTracking: () => void
stopTracking: () => void
updatePosition: (position: LocationObject) => void
setHeading: (heading: number, accuracy?: number) => void
setFollowUser: (follow: boolean) => void
setUserPannedMap: (panned: boolean) => void
setLocationAuthStatus: (status: string) => void
markStale: () => void
}
const STALE_THRESHOLD_MS = 5000 // 5 seconds
export const useLocationStore = create<LocationState>((set, get) => ({
// Initial state
currentPosition: null,
heading: null,
headingAccuracy: null,
accuracy: null,
speed: null,
course: null,
isTracking: false,
isStale: false,
lastUpdateTimestamp: null,
updateIntervalMs: null,
followUser: true,
userPannedMap: false,
locationAuthStatus: null,
startTracking: () => {
set({ isTracking: true, isStale: false })
console.log('[LocationStore] Tracking started')
},
stopTracking: () => {
set({
isTracking: false,
isStale: true,
currentPosition: null,
heading: null,
speed: null,
course: null,
})
console.log('[LocationStore] Tracking stopped')
},
updatePosition: (position: LocationObject) => {
const now = Date.now()
const prevTimestamp = get().lastUpdateTimestamp
const interval = prevTimestamp ? now - prevTimestamp : null
set({
currentPosition: position,
accuracy: position.coords.accuracy ?? null,
speed: position.coords.speed ?? null,
course: position.coords.course ?? null,
lastUpdateTimestamp: now,
updateIntervalMs: interval,
isStale: false,
})
console.log(
`[LocationStore] Position updated: lat=${position.coords.latitude.toFixed(6)}, ` +
`lng=${position.coords.longitude.toFixed(6)}, accuracy=${position.coords.accuracy}m, ` +
`speed=${position.coords.speed ?? 'null'}m/s, interval=${interval ?? 'first'}ms`
)
},
setHeading: (heading: number, accuracy?: number) => {
set({
heading,
headingAccuracy: accuracy ?? null,
})
console.log(`[LocationStore] Heading updated: ${heading.toFixed(1)}° (accuracy: ${accuracy ?? 'unknown'})`)
},
setFollowUser: (follow: boolean) => {
set({ followUser: follow })
if (follow) {
set({ userPannedMap: false })
}
},
setUserPannedMap: (panned: boolean) => {
set({ userPannedMap: panned })
if (panned) {
set({ followUser: false })
}
},
setLocationAuthStatus: (status: string) => {
set({ locationAuthStatus: status })
console.log(`[LocationStore] Auth status: ${status}`)
},
markStale: () => {
const { lastUpdateTimestamp } = get()
if (lastUpdateTimestamp && Date.now() - lastUpdateTimestamp > STALE_THRESHOLD_MS) {
set({ isStale: true })
console.log('[LocationStore] Position marked stale')
}
},
}))
// Selector hooks for performance
export const selectCurrentPosition = (state: LocationState) => state.currentPosition
export const selectIsTracking = (state: LocationState) => state.isTracking
export const selectIsStale = (state: LocationState) => state.isStale
export const selectHeading = (state: LocationState) => state.heading
export const selectAccuracy = (state: LocationState) => state.accuracy
export const selectSpeed = (state: LocationState) => state.speed
export const selectFollowUser = (state: LocationState) => state.followUser
export const selectLocationAuthStatus = (state: LocationState) => state.locationAuthStatus
@@ -1,200 +0,0 @@
/**
* Offline Manager
* Handle offline queue, sync, and storage
*/
import AsyncStorage from '@react-native-async-storage/async-storage';
import NetInfo from '@react-native-community/netinfo';
const QUEUE_KEY = '@quixzoom:observationQueue';
const SYNC_INTERVAL = 30000; // 30 seconds
/**
* Add observation to offline queue
*/
export async function queueObservation(observation) {
try {
const queue = await getQueue();
const queuedObservation = {
...observation,
id: `queued_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
queuedAt: new Date().toISOString(),
retryCount: 0,
};
queue.push(queuedObservation);
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
return queuedObservation.id;
} catch (error) {
console.error('Failed to queue observation:', error);
throw error;
}
}
/**
* Get offline queue
*/
export async function getQueue() {
try {
const queueJson = await AsyncStorage.getItem(QUEUE_KEY);
return queueJson ? JSON.parse(queueJson) : [];
} catch (error) {
console.error('Failed to get queue:', error);
return [];
}
}
/**
* Remove observation from queue
*/
export async function removeFromQueue(observationId) {
try {
const queue = await getQueue();
const filtered = queue.filter(obs => obs.id !== observationId);
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(filtered));
return filtered.length;
} catch (error) {
console.error('Failed to remove from queue:', error);
throw error;
}
}
/**
* Update observation in queue
*/
export async function updateInQueue(observationId, updates) {
try {
const queue = await getQueue();
const index = queue.findIndex(obs => obs.id === observationId);
if (index !== -1) {
queue[index] = { ...queue[index], ...updates };
await AsyncStorage.setItem(QUEUE_KEY, JSON.stringify(queue));
}
} catch (error) {
console.error('Failed to update queue:', error);
}
}
/**
* Clear queue
*/
export async function clearQueue() {
try {
await AsyncStorage.removeItem(QUEUE_KEY);
} catch (error) {
console.error('Failed to clear queue:', error);
}
}
/**
* Get queue count
*/
export async function getQueueCount() {
const queue = await getQueue();
return queue.length;
}
/**
* Check if device is online
*/
export async function isOnline() {
const netInfo = await NetInfo.fetch();
return netInfo.isConnected && netInfo.isInternetReachable;
}
/**
* Sync queue with server
*/
export async function syncQueue(apiClient) {
try {
const online = await isOnline();
if (!online) {
console.log('Device is offline, skipping sync');
return { synced: 0, failed: 0 };
}
const queue = await getQueue();
if (queue.length === 0) {
return { synced: 0, failed: 0 };
}
console.log(`Syncing ${queue.length} observations...`);
let synced = 0;
let failed = 0;
for (const observation of queue) {
try {
// Attempt to upload
await apiClient.observations.create(observation);
await removeFromQueue(observation.id);
synced++;
} catch (error) {
console.error(`Failed to sync observation ${observation.id}:`, error);
// Increment retry count
const newRetryCount = (observation.retryCount || 0) + 1;
if (newRetryCount >= 5) {
// Max retries reached, remove from queue
await removeFromQueue(observation.id);
console.log(`Observation ${observation.id} removed after max retries`);
} else {
await updateInQueue(observation.id, { retryCount: newRetryCount });
}
failed++;
}
}
console.log(`Sync complete: ${synced} synced, ${failed} failed`);
return { synced, failed };
} catch (error) {
console.error('Sync failed:', error);
return { synced: 0, failed: 0, error: error.message };
}
}
/**
* Start automatic sync
*/
export function startAutoSync(apiClient, interval = SYNC_INTERVAL) {
console.log('Starting auto sync...');
// Initial sync
syncQueue(apiClient);
// Periodic sync
const syncInterval = setInterval(() => {
syncQueue(apiClient);
}, interval);
// Listen for network changes
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected && state.isInternetReachable) {
console.log('Network restored, triggering sync...');
syncQueue(apiClient);
}
});
// Return cleanup function
return () => {
clearInterval(syncInterval);
unsubscribe();
};
}
/**
* Get queue status
*/
export async function getQueueStatus() {
const queue = await getQueue();
const total = queue.length;
const pending = queue.filter(obs => (obs.retryCount || 0) === 0).length;
const retrying = queue.filter(obs => (obs.retryCount || 0) > 0).length;
return {
total,
pending,
retrying,
oldestObservation: total > 0 ? queue[0].queuedAt : null,
};
}
-128
View File
@@ -1,128 +0,0 @@
/**
* Permissions Utility
* Handle camera, location, and storage permissions
*/
import { Platform, PermissionsAndroid } from 'react-native';
import { check, request, PERMISSIONS, RESULTS } from 'react-native-permissions';
/**
* Request camera permission
*/
export async function requestCameraPermission() {
if (Platform.OS === 'ios') {
const result = await check(PERMISSIONS.IOS.CAMERA);
if (result === RESULTS.DENIED) {
return await request(PERMISSIONS.IOS.CAMERA);
}
return result;
} else {
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.CAMERA,
{
title: 'Camera Permission',
message: 'quiXzoom needs camera access to capture observations.',
buttonPositive: 'Allow',
buttonNegative: 'Deny',
}
);
return result === PermissionsAndroid.RESULTS.GRANTED ? RESULTS.GRANTED : RESULTS.DENIED;
}
}
/**
* Request location permission
*/
export async function requestLocationPermission() {
if (Platform.OS === 'ios') {
const result = await check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
if (result === RESULTS.DENIED) {
return await request(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE);
}
return result;
} else {
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
{
title: 'Location Permission',
message: 'quiXzoom needs location access to tag observations with GPS.',
buttonPositive: 'Allow',
buttonNegative: 'Deny',
}
);
return result === PermissionsAndroid.RESULTS.GRANTED ? RESULTS.GRANTED : RESULTS.DENIED;
}
}
/**
* Request storage permission
*/
export async function requestStoragePermission() {
if (Platform.OS === 'ios') {
const result = await check(PERMISSIONS.IOS.PHOTO_LIBRARY);
if (result === RESULTS.DENIED) {
return await request(PERMISSIONS.IOS.PHOTO_LIBRARY);
}
return result;
} else {
const result = await PermissionsAndroid.request(
PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE,
{
title: 'Storage Permission',
message: 'quiXzoom needs storage access to save observations.',
buttonPositive: 'Allow',
buttonNegative: 'Deny',
}
);
return result === PermissionsAndroid.RESULTS.GRANTED ? RESULTS.GRANTED : RESULTS.DENIED;
}
}
/**
* Request all required permissions
*/
export async function requestAllPermissions() {
const results = await Promise.all([
requestCameraPermission(),
requestLocationPermission(),
requestStoragePermission(),
]);
return {
camera: results[0],
location: results[1],
storage: results[2],
allGranted: results.every(r => r === RESULTS.GRANTED),
};
}
/**
* Check if all permissions are granted
*/
export async function checkPermissions() {
if (Platform.OS === 'ios') {
const [camera, location, storage] = await Promise.all([
check(PERMISSIONS.IOS.CAMERA),
check(PERMISSIONS.IOS.LOCATION_WHEN_IN_USE),
check(PERMISSIONS.IOS.PHOTO_LIBRARY),
]);
return {
camera,
location,
storage,
allGranted: camera === RESULTS.GRANTED && location === RESULTS.GRANTED && storage === RESULTS.GRANTED,
};
} else {
const [camera, location, storage] = await Promise.all([
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.CAMERA),
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION),
PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.READ_EXTERNAL_STORAGE),
]);
return {
camera: camera ? RESULTS.GRANTED : RESULTS.DENIED,
location: location ? RESULTS.GRANTED : RESULTS.DENIED,
storage: storage ? RESULTS.GRANTED : RESULTS.DENIED,
allGranted: camera && location && storage,
};
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"paths": {
"@/*": ["src/*"]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.d.ts",
"expo-env.d.ts"
]
}