diff --git a/app.json b/app.json index 76f6552..832bbed 100644 --- a/app.json +++ b/app.json @@ -9,7 +9,11 @@ "userInterfaceStyle": "automatic", "newArchEnabled": true, "ios": { - "supportsTablet": true + "supportsTablet": true, + "bundleIdentifier": "com.wellnuo.app", + "infoPlist": { + "ITSAppUsesNonExemptEncryption": false + } }, "android": { "adaptiveIcon": { @@ -43,6 +47,13 @@ "experiments": { "typedRoutes": true, "reactCompiler": true - } + }, + "extra": { + "router": {}, + "eas": { + "projectId": "4a77e46d-7b0e-4ace-a385-006b07027234" + } + }, + "owner": "kosyakorel1" } } diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx index 71dd5c0..84b0b88 100644 --- a/app/(tabs)/_layout.tsx +++ b/app/(tabs)/_layout.tsx @@ -1,6 +1,6 @@ import { Tabs } from 'expo-router'; import React from 'react'; -import { Ionicons } from '@expo/vector-icons'; +import { Feather } from '@expo/vector-icons'; import { HapticTab } from '@/components/haptic-tab'; import { AppColors } from '@/constants/theme'; @@ -18,6 +18,13 @@ export default function TabLayout() { tabBarStyle: { backgroundColor: isDark ? '#151718' : AppColors.background, borderTopColor: isDark ? '#2D3135' : AppColors.border, + height: 85, + paddingBottom: 25, + paddingTop: 10, + }, + tabBarLabelStyle: { + fontSize: 11, + fontWeight: '500', }, headerShown: false, tabBarButton: HapticTab, @@ -26,13 +33,13 @@ export default function TabLayout() { ( - + ), }} /> - {/* Hide dashboard - now accessed via beneficiary selection */} + {/* Hide old dashboard - now index shows WebView dashboard */} ( - + ), }} /> @@ -53,7 +60,7 @@ export default function TabLayout() { options={{ title: 'Profile', tabBarIcon: ({ color, size }) => ( - + ), }} /> @@ -64,6 +71,13 @@ export default function TabLayout() { href: null, }} /> + {/* Beneficiaries - hidden from tab bar but keeps tab bar visible */} + ); } diff --git a/app/beneficiaries/[id]/dashboard.tsx b/app/(tabs)/beneficiaries/[id]/dashboard.tsx similarity index 71% rename from app/beneficiaries/[id]/dashboard.tsx rename to app/(tabs)/beneficiaries/[id]/dashboard.tsx index 03ccf53..22facf7 100644 --- a/app/beneficiaries/[id]/dashboard.tsx +++ b/app/(tabs)/beneficiaries/[id]/dashboard.tsx @@ -4,10 +4,13 @@ import { WebView } from 'react-native-webview'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; import { useLocalSearchParams, router } from 'expo-router'; +import * as SecureStore from 'expo-secure-store'; import { useBeneficiary } from '@/contexts/BeneficiaryContext'; import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; import { FullScreenError } from '@/components/ui/ErrorMessage'; +// Start with login page, then redirect to dashboard after auth +const LOGIN_URL = 'https://react.eluxnetworks.net/login'; const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard'; export default function BeneficiaryDashboardScreen() { @@ -17,9 +20,56 @@ export default function BeneficiaryDashboardScreen() { const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [canGoBack, setCanGoBack] = useState(false); + const [authToken, setAuthToken] = useState(null); + const [userName, setUserName] = useState(null); + const [userId, setUserId] = useState(null); + const [isTokenLoaded, setIsTokenLoaded] = useState(false); + const [webViewUrl, setWebViewUrl] = useState(DASHBOARD_URL); const beneficiaryName = currentBeneficiary?.name || 'Dashboard'; + // Load token, username, and userId from SecureStore + useEffect(() => { + const loadCredentials = async () => { + try { + const token = await SecureStore.getItemAsync('accessToken'); + const user = await SecureStore.getItemAsync('userName'); + const uid = await SecureStore.getItemAsync('userId'); + setAuthToken(token); + setUserName(user); + setUserId(uid); + console.log('Loaded credentials for WebView:', { hasToken: !!token, user, uid }); + } catch (err) { + console.error('Failed to load credentials:', err); + } finally { + setIsTokenLoaded(true); + } + }; + loadCredentials(); + }, []); + + // JavaScript to inject token into localStorage before page loads + // Web app uses auth2 key with JSON object: {username, token, user_id} + const injectedJavaScript = authToken + ? ` + (function() { + try { + // Web app expects auth2 as JSON object with these exact fields + var authData = { + username: '${userName || ''}', + token: '${authToken}', + user_id: ${userId || 'null'} + }; + localStorage.setItem('auth2', JSON.stringify(authData)); + console.log('Auth data injected:', authData.username, 'user_id:', authData.user_id); + } catch(e) { + console.error('Failed to inject token:', e); + } + })(); + true; + ` + : ''; + const handleRefresh = () => { setError(null); setIsLoading(true); @@ -45,6 +95,25 @@ export default function BeneficiaryDashboardScreen() { router.back(); }; + // Wait for token to load before showing WebView + if (!isTokenLoaded) { + return ( + + + + + + {beneficiaryName} + + + + + Preparing dashboard... + + + ); + } + if (error) { return ( @@ -100,7 +169,7 @@ export default function BeneficiaryDashboardScreen() { setIsLoading(true)} onLoadEnd={() => setIsLoading(false)} @@ -112,6 +181,10 @@ export default function BeneficiaryDashboardScreen() { startInLoadingState={true} scalesPageToFit={true} allowsBackForwardNavigationGestures={true} + // Inject token into localStorage BEFORE content loads + injectedJavaScriptBeforeContentLoaded={injectedJavaScript} + // Also inject after load in case page reads localStorage late + injectedJavaScript={injectedJavaScript} renderLoading={() => ( @@ -144,7 +217,7 @@ export default function BeneficiaryDashboardScreen() { router.push(`/beneficiaries/${id}`)} + onPress={() => router.push(`./`)} > Details diff --git a/app/beneficiaries/[id]/index.tsx b/app/(tabs)/beneficiaries/[id]/index.tsx similarity index 100% rename from app/beneficiaries/[id]/index.tsx rename to app/(tabs)/beneficiaries/[id]/index.tsx diff --git a/app/beneficiaries/_layout.tsx b/app/(tabs)/beneficiaries/_layout.tsx similarity index 100% rename from app/beneficiaries/_layout.tsx rename to app/(tabs)/beneficiaries/_layout.tsx diff --git a/app/(tabs)/index.tsx b/app/(tabs)/index.tsx index 1eb609e..e1fe67d 100644 --- a/app/(tabs)/index.tsx +++ b/app/(tabs)/index.tsx @@ -1,143 +1,127 @@ -import React, { useState, useEffect, useCallback } from 'react'; -import { - View, - Text, - StyleSheet, - FlatList, - TouchableOpacity, - RefreshControl, -} from 'react-native'; -import { router } from 'expo-router'; +import React, { useState, useRef, useEffect } from 'react'; +import { View, Text, StyleSheet, ActivityIndicator, TouchableOpacity } from 'react-native'; +import { WebView } from 'react-native-webview'; import { Ionicons } from '@expo/vector-icons'; import { SafeAreaView } from 'react-native-safe-area-context'; -import { api } from '@/services/api'; +import * as SecureStore from 'expo-secure-store'; import { useAuth } from '@/contexts/AuthContext'; -import { useBeneficiary } from '@/contexts/BeneficiaryContext'; -import { LoadingSpinner } from '@/components/ui/LoadingSpinner'; -import { FullScreenError } from '@/components/ui/ErrorMessage'; -import { AppColors, BorderRadius, FontSizes, Spacing } from '@/constants/theme'; -import type { Beneficiary } from '@/types'; +import { AppColors, FontSizes, Spacing } from '@/constants/theme'; -export default function BeneficiariesListScreen() { +const DASHBOARD_URL = 'https://react.eluxnetworks.net/dashboard'; + +export default function HomeScreen() { const { user } = useAuth(); - const { setCurrentBeneficiary } = useBeneficiary(); - const [beneficiaries, setBeneficiaries] = useState([]); + const webViewRef = useRef(null); const [isLoading, setIsLoading] = useState(true); - const [isRefreshing, setIsRefreshing] = useState(false); const [error, setError] = useState(null); + const [canGoBack, setCanGoBack] = useState(false); + const [authToken, setAuthToken] = useState(null); + const [userName, setUserName] = useState(null); + const [userId, setUserId] = useState(null); + const [isTokenLoaded, setIsTokenLoaded] = useState(false); - const loadBeneficiaries = useCallback(async (showLoading = true) => { - if (showLoading) setIsLoading(true); - setError(null); - - try { - const response = await api.getBeneficiaries(); - - if (response.ok && response.data) { - setBeneficiaries(response.data.beneficiaries); - } else { - setError(response.error?.message || 'Failed to load beneficiaries'); + // Load credentials from SecureStore + useEffect(() => { + const loadCredentials = async () => { + try { + const token = await SecureStore.getItemAsync('accessToken'); + const user = await SecureStore.getItemAsync('userName'); + const uid = await SecureStore.getItemAsync('userId'); + setAuthToken(token); + setUserName(user); + setUserId(uid); + console.log('Home: Loaded credentials for WebView:', { hasToken: !!token, user, uid }); + } catch (err) { + console.error('Failed to load credentials:', err); + } finally { + setIsTokenLoaded(true); } - } catch (err) { - setError(err instanceof Error ? err.message : 'An error occurred'); - } finally { - setIsLoading(false); - setIsRefreshing(false); - } + }; + loadCredentials(); }, []); - useEffect(() => { - loadBeneficiaries(); - }, [loadBeneficiaries]); + // JavaScript to inject auth token into localStorage + // Web app expects auth2 as JSON: {username, token, user_id} + const injectedJavaScript = authToken + ? ` + (function() { + try { + var authData = { + username: '${userName || ''}', + token: '${authToken}', + user_id: ${userId || 'null'} + }; + localStorage.setItem('auth2', JSON.stringify(authData)); + console.log('Auth injected:', authData.username); + } catch(e) { + console.error('Failed to inject token:', e); + } + })(); + true; + ` + : ''; - const handleRefresh = useCallback(() => { - setIsRefreshing(true); - loadBeneficiaries(false); - }, [loadBeneficiaries]); - - const handleBeneficiaryPress = (beneficiary: Beneficiary) => { - // Set current beneficiary in context before navigating - setCurrentBeneficiary(beneficiary); - // Navigate directly to their dashboard - router.push(`/beneficiaries/${beneficiary.id}/dashboard`); + const handleRefresh = () => { + setError(null); + setIsLoading(true); + webViewRef.current?.reload(); }; - const renderBeneficiaryCard = ({ item }: { item: Beneficiary }) => ( - handleBeneficiaryPress(item)} - activeOpacity={0.7} - > - - - - {item.name.charAt(0).toUpperCase()} - - - + const handleWebViewBack = () => { + if (canGoBack) { + webViewRef.current?.goBack(); + } + }; - - {item.name} - {item.relationship} - - {' '} - {item.last_activity} - - - + const handleNavigationStateChange = (navState: any) => { + setCanGoBack(navState.canGoBack); + }; - {item.sensor_data && ( - - - - - {item.sensor_data.motion_detected ? 'Active' : 'Inactive'} - - Motion - - - - - {item.sensor_data.door_status === 'open' ? 'Open' : 'Closed'} - - Door - - - - {item.sensor_data.temperature}° - Temp + const handleError = () => { + setError('Failed to load dashboard. Please check your internet connection.'); + setIsLoading(false); + }; + + // Wait for token to load + if (!isTokenLoaded) { + return ( + + + + Hello, {user?.user_name || 'User'} + Dashboard - )} - - - - ); - - if (isLoading) { - return ; + + + Preparing dashboard... + + + ); } if (error) { - return loadBeneficiaries()} />; + return ( + + + + Hello, {user?.user_name || 'User'} + Dashboard + + + + + + + + Connection Error + {error} + + Try Again + + + + ); } return ( @@ -145,40 +129,53 @@ export default function BeneficiariesListScreen() { {/* Header */} - - Hello, {user?.user_name || 'User'} - - Beneficiaries + Hello, {user?.user_name || 'User'} + Dashboard + + + {canGoBack && ( + + + + )} + + + - - - - {/* Beneficiary List */} - item.id.toString()} - renderItem={renderBeneficiaryCard} - contentContainerStyle={styles.listContent} - showsVerticalScrollIndicator={false} - refreshControl={ - - } - ListEmptyComponent={ - - - No beneficiaries yet - - Add your first beneficiary to start monitoring - + {/* WebView Dashboard */} + + setIsLoading(true)} + onLoadEnd={() => setIsLoading(false)} + onError={handleError} + onHttpError={handleError} + onNavigationStateChange={handleNavigationStateChange} + javaScriptEnabled={true} + domStorageEnabled={true} + startInLoadingState={true} + scalesPageToFit={true} + allowsBackForwardNavigationGestures={true} + injectedJavaScriptBeforeContentLoaded={injectedJavaScript} + injectedJavaScript={injectedJavaScript} + renderLoading={() => ( + + + Loading dashboard... + + )} + /> + + {isLoading && ( + + - } - /> + )} + ); } @@ -186,7 +183,7 @@ export default function BeneficiariesListScreen() { const styles = StyleSheet.create({ container: { flex: 1, - backgroundColor: AppColors.surface, + backgroundColor: AppColors.background, }, header: { flexDirection: 'row', @@ -207,122 +204,76 @@ const styles = StyleSheet.create({ fontWeight: '700', color: AppColors.textPrimary, }, - addButton: { - width: 44, - height: 44, - borderRadius: BorderRadius.full, - backgroundColor: AppColors.primary, - justifyContent: 'center', + headerActions: { + flexDirection: 'row', alignItems: 'center', }, - listContent: { - padding: Spacing.md, + actionButton: { + padding: Spacing.xs, + marginLeft: Spacing.xs, }, - beneficiaryCard: { + refreshButton: { + padding: Spacing.xs, + }, + webViewContainer: { + flex: 1, + }, + webView: { + flex: 1, + }, + loadingContainer: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + justifyContent: 'center', + alignItems: 'center', backgroundColor: AppColors.background, - borderRadius: BorderRadius.lg, - padding: Spacing.md, - marginBottom: Spacing.md, - shadowColor: '#000', - shadowOffset: { width: 0, height: 2 }, - shadowOpacity: 0.05, - shadowRadius: 4, - elevation: 2, }, - beneficiaryInfo: { - flexDirection: 'row', - alignItems: 'center', - marginBottom: Spacing.md, - }, - avatarContainer: { - width: 56, - height: 56, - borderRadius: BorderRadius.full, - backgroundColor: AppColors.primaryLight, + loadingOverlay: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, justifyContent: 'center', alignItems: 'center', - marginRight: Spacing.md, + backgroundColor: 'rgba(255,255,255,0.8)', }, - avatarText: { - fontSize: FontSizes.xl, - fontWeight: '600', - color: AppColors.white, - }, - statusIndicator: { - position: 'absolute', - bottom: 2, - right: 2, - width: 14, - height: 14, - borderRadius: BorderRadius.full, - borderWidth: 2, - borderColor: AppColors.background, - }, - online: { - backgroundColor: AppColors.online, - }, - offline: { - backgroundColor: AppColors.offline, - }, - beneficiaryDetails: { - flex: 1, - }, - beneficiaryName: { - fontSize: FontSizes.lg, - fontWeight: '600', - color: AppColors.textPrimary, - }, - beneficiaryRelationship: { - fontSize: FontSizes.sm, - color: AppColors.textSecondary, - marginTop: 2, - }, - lastActivity: { - fontSize: FontSizes.xs, - color: AppColors.textMuted, - marginTop: 4, - }, - sensorStats: { - flexDirection: 'row', - justifyContent: 'space-around', - paddingTop: Spacing.md, - borderTopWidth: 1, - borderTopColor: AppColors.border, - }, - statItem: { - alignItems: 'center', - }, - statValue: { + loadingText: { + marginTop: Spacing.md, fontSize: FontSizes.base, - fontWeight: '600', - color: AppColors.textPrimary, - marginTop: Spacing.xs, + color: AppColors.textSecondary, }, - statLabel: { - fontSize: FontSizes.xs, - color: AppColors.textMuted, - }, - chevron: { - position: 'absolute', - top: Spacing.md, - right: Spacing.md, - }, - emptyContainer: { + errorContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', - paddingTop: Spacing.xxl * 2, + padding: Spacing.xl, }, - emptyTitle: { + errorTitle: { fontSize: FontSizes.lg, fontWeight: '600', color: AppColors.textPrimary, marginTop: Spacing.md, }, - emptyText: { + errorText: { fontSize: FontSizes.base, color: AppColors.textSecondary, textAlign: 'center', marginTop: Spacing.xs, }, + retryButton: { + marginTop: Spacing.lg, + paddingHorizontal: Spacing.xl, + paddingVertical: Spacing.md, + backgroundColor: AppColors.primary, + borderRadius: 8, + }, + retryButtonText: { + color: AppColors.white, + fontSize: FontSizes.base, + fontWeight: '600', + }, }); diff --git a/app/_layout.tsx b/app/_layout.tsx index 6eab4c0..e7ce559 100644 --- a/app/_layout.tsx +++ b/app/_layout.tsx @@ -44,7 +44,6 @@ function RootLayoutNav() { - diff --git a/assets/images/android-icon-background.png b/assets/images/android-icon-background.png index 5ffefc5..48aaf7e 100644 Binary files a/assets/images/android-icon-background.png and b/assets/images/android-icon-background.png differ diff --git a/assets/images/android-icon-foreground.png b/assets/images/android-icon-foreground.png index 3a9e501..f07797d 100644 Binary files a/assets/images/android-icon-foreground.png and b/assets/images/android-icon-foreground.png differ diff --git a/assets/images/android-icon-monochrome.png b/assets/images/android-icon-monochrome.png index 77484eb..e2e89c2 100644 Binary files a/assets/images/android-icon-monochrome.png and b/assets/images/android-icon-monochrome.png differ diff --git a/assets/images/favicon.png b/assets/images/favicon.png index 408bd74..c09b7fd 100644 Binary files a/assets/images/favicon.png and b/assets/images/favicon.png differ diff --git a/assets/images/icon.png b/assets/images/icon.png index 7165a53..0db9930 100644 Binary files a/assets/images/icon.png and b/assets/images/icon.png differ diff --git a/assets/images/splash-icon.png b/assets/images/splash-icon.png index 03d6f6b..db0b808 100644 Binary files a/assets/images/splash-icon.png and b/assets/images/splash-icon.png differ diff --git a/eas.json b/eas.json new file mode 100644 index 0000000..40a8970 --- /dev/null +++ b/eas.json @@ -0,0 +1,26 @@ +{ + "cli": { + "version": ">= 5.0.0", + "appVersionSource": "remote" + }, + "build": { + "development": { + "developmentClient": true, + "distribution": "internal" + }, + "preview": { + "distribution": "internal" + }, + "production": { + "autoIncrement": true + } + }, + "submit": { + "production": { + "ios": { + "appleId": "serter2069@gmail.com", + "ascAppId": "WILL_BE_SET_AFTER_FIRST_BUILD" + } + } + } +} diff --git a/package-lock.json b/package-lock.json index f6196ca..a1ce803 100644 --- a/package-lock.json +++ b/package-lock.json @@ -41,6 +41,7 @@ "eslint": "^9.25.0", "eslint-config-expo": "~10.0.0", "playwright": "^1.57.0", + "sharp": "^0.34.5", "typescript": "~5.9.2" } }, @@ -2275,6 +2276,496 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", + "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", + "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", + "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", + "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", + "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", + "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", + "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", + "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", + "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", + "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", + "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", + "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", + "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", + "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", + "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", + "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", + "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.2.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", + "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", + "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", + "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", + "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.7.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", + "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", + "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", + "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/balanced-match": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/@isaacs/balanced-match/-/balanced-match-4.0.1.tgz", @@ -11126,6 +11617,64 @@ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==", "license": "MIT" }, + "node_modules/sharp": { + "version": "0.34.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", + "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", + "dev": true, + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.2", + "semver": "^7.7.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.34.5", + "@img/sharp-darwin-x64": "0.34.5", + "@img/sharp-libvips-darwin-arm64": "1.2.4", + "@img/sharp-libvips-darwin-x64": "1.2.4", + "@img/sharp-libvips-linux-arm": "1.2.4", + "@img/sharp-libvips-linux-arm64": "1.2.4", + "@img/sharp-libvips-linux-ppc64": "1.2.4", + "@img/sharp-libvips-linux-riscv64": "1.2.4", + "@img/sharp-libvips-linux-s390x": "1.2.4", + "@img/sharp-libvips-linux-x64": "1.2.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", + "@img/sharp-libvips-linuxmusl-x64": "1.2.4", + "@img/sharp-linux-arm": "0.34.5", + "@img/sharp-linux-arm64": "0.34.5", + "@img/sharp-linux-ppc64": "0.34.5", + "@img/sharp-linux-riscv64": "0.34.5", + "@img/sharp-linux-s390x": "0.34.5", + "@img/sharp-linux-x64": "0.34.5", + "@img/sharp-linuxmusl-arm64": "0.34.5", + "@img/sharp-linuxmusl-x64": "0.34.5", + "@img/sharp-wasm32": "0.34.5", + "@img/sharp-win32-arm64": "0.34.5", + "@img/sharp-win32-ia32": "0.34.5", + "@img/sharp-win32-x64": "0.34.5" + } + }, + "node_modules/sharp/node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", diff --git a/package.json b/package.json index bcc3db2..17b3905 100644 --- a/package.json +++ b/package.json @@ -44,6 +44,7 @@ "eslint": "^9.25.0", "eslint-config-expo": "~10.0.0", "playwright": "^1.57.0", + "sharp": "^0.34.5", "typescript": "~5.9.2" }, "private": true