diff --git a/apps/mobile/__tests__/ProfileLink.test.tsx b/apps/mobile/__tests__/ProfileLink.test.tsx new file mode 100644 index 0000000..ea535b5 --- /dev/null +++ b/apps/mobile/__tests__/ProfileLink.test.tsx @@ -0,0 +1,23 @@ +import React from 'react'; +import { fireEvent, render, screen } from '@testing-library/react-native'; +import ProfileLink from '../src/components/ProfileLink'; + +describe('ProfileLink', () => { + test('renders the platform and username', () => { + render( + {}} />, + ); + + expect(screen.getByText('GitHub')).toBeTruthy(); + expect(screen.getByText('octocat')).toBeTruthy(); + }); + + test('calls onPress when pressed', () => { + const onPress = jest.fn(); + + render(); + fireEvent.press(screen.getByTestId('profile-link')); + + expect(onPress).toHaveBeenCalledTimes(1); + }); +}); diff --git a/apps/mobile/jest.setup.js b/apps/mobile/jest.setup.js index 6961e1e..7b88925 100644 --- a/apps/mobile/jest.setup.js +++ b/apps/mobile/jest.setup.js @@ -1,4 +1,3 @@ -/* eslint-env jest */ import 'react-native-gesture-handler/jestSetup'; global.__reanimatedWorkletInit = () => {}; diff --git a/apps/mobile/package.json b/apps/mobile/package.json index 8bb6ccf..61827c9 100644 --- a/apps/mobile/package.json +++ b/apps/mobile/package.json @@ -48,6 +48,7 @@ "@react-native/gradle-plugin": "0.84.1", "@react-native/metro-config": "0.84.1", "@react-native/typescript-config": "0.84.1", + "@testing-library/react-native": "^13.3.3", "@types/jest": "^29.5.13", "@types/react": "^19.1.17", "@types/react-native-vector-icons": "^6.4.18", diff --git a/apps/mobile/src/components/ProfileLink.tsx b/apps/mobile/src/components/ProfileLink.tsx new file mode 100644 index 0000000..bf4fb63 --- /dev/null +++ b/apps/mobile/src/components/ProfileLink.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import { + ActivityIndicator, + StyleSheet, + Text, + TouchableOpacity, + View, +} from 'react-native'; +import { PLATFORMS } from '@devcard/shared'; +import { BORDER_RADIUS, COLORS, FONT_SIZE, SPACING } from '../theme/tokens'; + +export type ProfileLinkStatus = 'idle' | 'loading' | 'success' | 'error'; + +type ProfileLinkProps = { + platform: string; + username: string; + onPress: () => void; + actionLabel?: string; + status?: ProfileLinkStatus; +}; + +export default function ProfileLink({ + platform, + username, + onPress, + actionLabel = 'Open', + status = 'idle', +}: ProfileLinkProps) { + const platformDef = PLATFORMS[platform]; + const platformName = platformDef?.name || platform; + const isLoading = status === 'loading'; + + return ( + + + {platformName.charAt(0) || '?'} + + + {platformName} + {username} + + + {isLoading ? ( + + ) : ( + + {actionLabel} + + )} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + alignItems: 'center', + backgroundColor: COLORS.bgCard, + borderRadius: BORDER_RADIUS.md, + padding: SPACING.md, + borderWidth: 1, + borderColor: COLORS.border, + }, + containerDone: { + borderColor: COLORS.success, + backgroundColor: 'rgba(34, 197, 94, 0.05)', + }, + icon: { + width: 40, + height: 40, + borderRadius: 10, + alignItems: 'center', + justifyContent: 'center', + }, + iconText: { + color: COLORS.white, + fontWeight: '700', + fontSize: FONT_SIZE.md, + }, + info: { + flex: 1, + marginLeft: SPACING.md, + }, + platform: { + fontSize: FONT_SIZE.md, + fontWeight: '600', + color: COLORS.textPrimary, + }, + username: { + fontSize: FONT_SIZE.sm, + color: COLORS.textMuted, + marginTop: 1, + }, + action: { + backgroundColor: COLORS.primary, + borderRadius: BORDER_RADIUS.sm, + paddingHorizontal: SPACING.md, + paddingVertical: SPACING.xs, + minWidth: 72, + alignItems: 'center', + }, + actionDone: { + backgroundColor: COLORS.success, + }, + actionLoading: { + backgroundColor: COLORS.primaryDark, + }, + actionText: { + color: COLORS.white, + fontWeight: '700', + fontSize: FONT_SIZE.sm, + }, + actionTextDone: {}, +}); diff --git a/apps/mobile/src/navigation/MainTabs.tsx b/apps/mobile/src/navigation/MainTabs.tsx index e70a5d6..c43bd81 100644 --- a/apps/mobile/src/navigation/MainTabs.tsx +++ b/apps/mobile/src/navigation/MainTabs.tsx @@ -63,6 +63,24 @@ function TabIcon({ name, focused }: { name: string; focused: boolean }) { ); } +const renderHomeIcon = ({ focused }: { focused: boolean }) => ( + +); +const renderLinksIcon = ({ focused }: { focused: boolean }) => ( + +); +const renderCardsIcon = ({ focused }: { focused: boolean }) => ( + +); +const renderSettingsIcon = ({ focused }: { focused: boolean }) => ( + +); +const renderScanIcon = () => ( + + 📷 + +); + // ─── Tab Navigator ─── const Tab = createBottomTabNavigator(); @@ -70,32 +88,41 @@ const Tab = createBottomTabNavigator(); function TabNavigator() { return ( ({ + screenOptions={{ headerShown: false, tabBarStyle: styles.tabBar, tabBarActiveTintColor: COLORS.primary, tabBarInactiveTintColor: COLORS.textMuted, tabBarLabelStyle: styles.tabLabel, - tabBarIcon: ({ focused }) => ( - - ), - })}> - - + }}> + + ( - - 📷 - - ), + tabBarIcon: renderScanIcon, }} /> - - + + ); } diff --git a/apps/mobile/src/screens/CardsScreen.tsx b/apps/mobile/src/screens/CardsScreen.tsx index 023ceb4..6898edd 100644 --- a/apps/mobile/src/screens/CardsScreen.tsx +++ b/apps/mobile/src/screens/CardsScreen.tsx @@ -93,7 +93,7 @@ export default function CardsScreen() { setSelectedLinkIds([]); fetchData(); } - } catch (err) { + } catch { Alert.alert('Error', 'Failed to create card'); } }; diff --git a/apps/mobile/src/screens/DevCardViewScreen.tsx b/apps/mobile/src/screens/DevCardViewScreen.tsx index a0d1f3d..a389a44 100644 --- a/apps/mobile/src/screens/DevCardViewScreen.tsx +++ b/apps/mobile/src/screens/DevCardViewScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { View, Text, @@ -9,12 +9,12 @@ import { Linking, Clipboard, StatusBar, - ActivityIndicator, Alert, } from 'react-native'; import { SafeAreaView } from 'react-native-safe-area-context'; import { COLORS, SPACING, FONT_SIZE, BORDER_RADIUS, SHADOWS } from '../theme/tokens'; import { Skeleton } from '../components/Skeleton'; +import ProfileLink from '../components/ProfileLink'; import { PLATFORMS, getProfileUrl, getWebViewUrl } from '@devcard/shared'; import { API_BASE_URL } from '../config'; import { useAuth } from '../context/AuthContext'; @@ -133,7 +133,11 @@ export default function DevCardViewScreen({ navigation, route }: Props) { } finally { setLoading(false); } - }; + }, [username]); + + useEffect(() => { + fetchProfile(); + }, [fetchProfile]); // ─── Hybrid Follow Engine ─── @@ -305,7 +309,11 @@ export default function DevCardViewScreen({ navigation, route }: Props) { - + @@ -317,9 +325,20 @@ export default function DevCardViewScreen({ navigation, route }: Props) { {/* Tiles Skeleton */} - + {[1, 2, 3].map(i => ( + + + @@ -420,13 +439,18 @@ export default function DevCardViewScreen({ navigation, route }: Props) { {profile.links.map(link => { - const platform = PLATFORMS[link.platform]; const state = followStates[link.id] || 'idle'; const btnColor = getButtonColor(link, state); const isDone = state === 'success'; return ( - handlePlatformAction(link)} + /> style={[styles.platformTile, isDone && styles.tileDone]} onPress={() => handlePlatformAction(link)} onLongPress={() => { @@ -572,6 +596,9 @@ const styles = StyleSheet.create({ // ─── Tiles ─── tilesSection: { gap: SPACING.sm }, + headerSkeletonLine: { marginBottom: 8 }, + tilesSkeletonLabel: { marginBottom: 12 }, + tileSkeletonLine: { marginBottom: 6 }, tilesHeader: { flexDirection: 'row', alignItems: 'center', justifyContent: 'space-between', marginBottom: SPACING.xs, diff --git a/apps/mobile/src/screens/HomeScreen.tsx b/apps/mobile/src/screens/HomeScreen.tsx index c5c2eec..70aecd0 100644 --- a/apps/mobile/src/screens/HomeScreen.tsx +++ b/apps/mobile/src/screens/HomeScreen.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; import { View, Text, @@ -44,11 +44,7 @@ export default function HomeScreen({ navigation }: Props) { ? `${APP_URL}/devcard/${user.defaultCardId}` : `${APP_URL}/u/${user?.username}`; - useEffect(() => { - fetchData(); - }, []); - - const fetchData = async () => { + const fetchData = useCallback(async () => { try { const [profileRes, analyticsRes] = await Promise.all([ fetch(`${API_BASE_URL}/api/profiles/me`, { @@ -69,7 +65,11 @@ export default function HomeScreen({ navigation }: Props) { } catch (err) { console.error('Failed to fetch dashboard data:', err); } - }; + }, [token]); + + useEffect(() => { + fetchData(); + }, [fetchData]); const onRefresh = async () => { setRefreshing(true); diff --git a/apps/mobile/src/screens/LinksScreen.tsx b/apps/mobile/src/screens/LinksScreen.tsx index daded55..e3d762c 100644 --- a/apps/mobile/src/screens/LinksScreen.tsx +++ b/apps/mobile/src/screens/LinksScreen.tsx @@ -70,7 +70,7 @@ export default function LinksScreen() { setUsernameInput(''); fetchLinks(); } - } catch (err) { + } catch { Alert.alert('Error', 'Failed to add link'); } }; @@ -88,7 +88,7 @@ export default function LinksScreen() { headers: { Authorization: `Bearer ${token}` }, }); fetchLinks(); - } catch (err) { + } catch { Alert.alert('Error', 'Failed to remove link'); } }, diff --git a/apps/mobile/src/screens/SettingsScreen.tsx b/apps/mobile/src/screens/SettingsScreen.tsx index d60599d..6fb96e3 100644 --- a/apps/mobile/src/screens/SettingsScreen.tsx +++ b/apps/mobile/src/screens/SettingsScreen.tsx @@ -50,7 +50,7 @@ export default function SettingsScreen() { } else { Alert.alert('Error', 'Failed to update profile'); } - } catch (err) { + } catch { Alert.alert('Error', 'Something went wrong'); } finally { setSaving(false); diff --git a/apps/mobile/src/screens/WebViewScreen.tsx b/apps/mobile/src/screens/WebViewScreen.tsx index 10f9837..77882e7 100644 --- a/apps/mobile/src/screens/WebViewScreen.tsx +++ b/apps/mobile/src/screens/WebViewScreen.tsx @@ -29,6 +29,7 @@ type Props = { * tap the native Follow/Connect button without leaving DevCard. */ export default function WebViewScreen({ navigation, route }: Props) { + const { profileUrl, displayName } = route.params; const { platform, url, @@ -406,6 +407,28 @@ export default function WebViewScreen({ navigation, route }: Props) { )} {/* WebView */} + ( + + Loading {displayName}... + + )} + onNavigationStateChange={() => { + // If user navigates away from the profile page, + // they likely completed the action + // We could auto-close here in the future + }} + /> + + {/* Done Button */} {url ? ( =18.0.0} + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/environment@29.7.0': resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1615,6 +1622,10 @@ packages: resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/get-type@30.1.0': + resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/globals@29.7.0': resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -1632,6 +1643,10 @@ packages: resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/source-map@29.6.3': resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -2096,6 +2111,9 @@ packages: '@sinclair/typebox@0.27.10': resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} + '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} @@ -2146,6 +2164,17 @@ packages: svelte: ^5.0.0 vite: ^6.3.0 || ^7.0.0 + '@testing-library/react-native@13.3.3': + resolution: {integrity: sha512-k6Mjsd9dbZgvY4Bl7P1NIpePQNi+dfYtlJ5voi9KQlynxSyQkfOgJmYGCYmw/aSgH/rUcFvG8u5gd4npzgRDyg==} + engines: {node: '>=18'} + peerDependencies: + jest: '>=29.0.0' + react: '>=18.2.0' + react-native: '>=0.71' + react-test-renderer: '>=18.2.0' + peerDependenciesMeta: + jest: + optional: true '@tybys/wasm-util@0.10.2': resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} @@ -4110,6 +4139,9 @@ packages: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} indent-string@5.0.0: resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} engines: {node: '>=12'} @@ -4375,6 +4407,10 @@ packages: resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-docblock@29.7.0: resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4403,6 +4439,10 @@ packages: resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@29.7.0: resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -4900,6 +4940,10 @@ packages: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + minimalistic-assert@1.0.1: resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} @@ -5293,6 +5337,10 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + prisma@6.19.2: resolution: {integrity: sha512-XTKeKxtQElcq3U9/jHyxSPgiRgeYDKxWTPOf6NkXA0dNj5j40MfEsZkMbyNpwDWCUv7YBFUl7I2VK/6ALbmhEg==} engines: {node: '>=18.18'} @@ -5404,6 +5452,11 @@ packages: react-is@19.2.4: resolution: {integrity: sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + + react-native-gesture-handler@2.31.2: + resolution: {integrity: sha512-rw5q74i2AfS7YGYdbxQDhOU7xqgY6WRM1132/CCm3erqjblhECZDZFHIm0tteHoC9ih24wogVBVVzcTBQtZ+5A==} react-native-gesture-handler@2.28.0: resolution: {integrity: sha512-0msfJ1vRxXKVgTgvL+1ZOoYw3/0z1R+Ked0+udoJhyplC2jbVKIJ8Z1bzWdpQRCV3QcQ87Op0zJVE5DhKK2A0A==} peerDependencies: @@ -5508,6 +5561,10 @@ packages: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + redis-errors@1.2.0: resolution: {integrity: sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==} engines: {node: '>=4'} @@ -5924,10 +5981,12 @@ packages: resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} engines: {node: '>=6'} + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} strip-indent@4.1.1: resolution: {integrity: sha512-SlyRoSkdh1dYP0PzclLE7r0M9sgbFKKMFXpFRUMNuKhQSbC6VQIGzq3E0qsfvGJaUFJPGv6Ws1NZ/haTAjfbMA==} engines: {node: '>=12'} - strip-json-comments@2.0.1: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} @@ -8150,6 +8209,8 @@ snapshots: dependencies: '@jest/types': 29.6.3 + '@jest/diff-sequences@30.4.0': {} + '@jest/environment@29.7.0': dependencies: '@jest/fake-timers': 29.7.0 @@ -8177,6 +8238,8 @@ snapshots: jest-mock: 29.7.0 jest-util: 29.7.0 + '@jest/get-type@30.1.0': {} + '@jest/globals@29.7.0': dependencies: '@jest/environment': 29.7.0 @@ -8219,6 +8282,10 @@ snapshots: dependencies: '@sinclair/typebox': 0.27.10 + '@jest/schemas@30.4.1': + dependencies: + '@sinclair/typebox': 0.34.49 + '@jest/source-map@29.6.3': dependencies: '@jridgewell/trace-mapping': 0.3.31 @@ -8877,6 +8944,8 @@ snapshots: '@sinclair/typebox@0.27.10': {} + '@sinclair/typebox@0.34.49': {} + '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 @@ -8929,6 +8998,24 @@ snapshots: magic-string: 0.30.21 obug: 2.1.1 svelte: 5.53.10 + vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) + vitefu: 1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) + + '@testing-library/react-native@13.3.3(jest@29.7.0(@types/node@22.19.15))(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react-test-renderer@19.2.3(react@19.2.3))(react@19.2.3)': + dependencies: + jest-matcher-utils: 30.4.1 + picocolors: 1.1.1 + pretty-format: 30.4.1 + react: 19.2.3 + react-native: 0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3) + react-test-renderer: 19.2.3(react@19.2.3) + redent: 3.0.0 + optionalDependencies: + jest: 29.7.0(@types/node@22.19.15) + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true vite: 7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2) vitefu: 1.1.2(vite@7.3.1(@types/node@22.19.15)(jiti@2.6.1)(lightningcss@1.32.0)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)) @@ -11255,6 +11342,7 @@ snapshots: imurmurhash@0.1.4: {} + indent-string@4.0.0: {} indent-string@5.0.0: {} inflight@1.0.6: @@ -11590,6 +11678,13 @@ snapshots: jest-get-type: 29.6.3 pretty-format: 29.7.0 + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + jest-docblock@29.7.0: dependencies: detect-newline: 3.1.0 @@ -11641,6 +11736,13 @@ snapshots: jest-get-type: 29.6.3 pretty-format: 29.7.0 + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + jest-message-util@29.7.0: dependencies: '@babel/code-frame': 7.29.0 @@ -12411,6 +12513,8 @@ snapshots: mimic-fn@2.1.0: {} + min-indent@1.0.1: {} + minimalistic-assert@1.0.1: {} minimatch@10.2.4: @@ -12796,6 +12900,13 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.6 + prisma@6.19.2(typescript@5.9.3): dependencies: '@prisma/config': 6.19.2 @@ -12912,6 +13023,9 @@ snapshots: react-is@19.2.4: {} + react-is@19.2.6: {} + + react-native-gesture-handler@2.31.2(react-native@0.84.1(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.2.14)(react@19.2.3))(react@19.2.3): react-native-gesture-handler@2.28.0(react-native@0.81.5(@babel/core@7.29.0)(@react-native-community/cli@20.1.0(typescript@5.9.3))(@react-native/metro-config@0.84.1(@babel/core@7.29.0))(@types/react@19.1.17)(react@19.1.0))(react@19.1.0): dependencies: '@egjs/hammerjs': 2.0.17 @@ -13076,6 +13190,11 @@ snapshots: real-require@0.2.0: {} + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + redis-errors@1.2.0: {} redis-parser@3.0.0: @@ -13551,8 +13670,10 @@ snapshots: strip-final-newline@2.0.0: {} + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 strip-indent@4.1.1: {} - strip-json-comments@2.0.1: {} strip-json-comments@3.1.1: {}