diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..bf39f24 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY=YOUR_CLERK_PUBLISHABLE_KEY diff --git a/app/(tabs)/_layout.tsx b/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..fa2766e --- /dev/null +++ b/app/(tabs)/_layout.tsx @@ -0,0 +1,36 @@ +import { BottomTabBar } from "@/components/bottom-tab-bar"; +import { useLanguageStore } from "@/store/UseLanguageStore"; +import { useAuth } from "@clerk/expo"; +import { Redirect, Tabs } from "expo-router"; + +export default function TabsLayout() { + const { isLoaded, isSignedIn } = useAuth(); + const hasHydratedLanguage = useLanguageStore((state) => state.hasHydrated); + const selectedLanguageId = useLanguageStore((state) => state.selectedLanguageId); + + if (!isLoaded || !hasHydratedLanguage) { + return null; + } + + if (!isSignedIn) { + return ; + } + + if (!selectedLanguageId) { + return ; + } + + return ( + } + > + + + + + + + ); +} diff --git a/app/(tabs)/ai-teacher.tsx b/app/(tabs)/ai-teacher.tsx new file mode 100644 index 0000000..2af7924 --- /dev/null +++ b/app/(tabs)/ai-teacher.tsx @@ -0,0 +1,10 @@ +import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen"; + +export default function AiTeacherScreen() { + return ( + + ); +} diff --git a/app/(tabs)/chat.tsx b/app/(tabs)/chat.tsx new file mode 100644 index 0000000..8d7e481 --- /dev/null +++ b/app/(tabs)/chat.tsx @@ -0,0 +1,10 @@ +import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen"; + +export default function ChatScreen() { + return ( + + ); +} diff --git a/app/(tabs)/home.tsx b/app/(tabs)/home.tsx new file mode 100644 index 0000000..6c3c719 --- /dev/null +++ b/app/(tabs)/home.tsx @@ -0,0 +1,10 @@ +import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen"; + +export default function HomeScreen() { + return ( + + ); +} diff --git a/app/(tabs)/learn.tsx b/app/(tabs)/learn.tsx new file mode 100644 index 0000000..972e130 --- /dev/null +++ b/app/(tabs)/learn.tsx @@ -0,0 +1,10 @@ +import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen"; + +export default function LearnScreen() { + return ( + + ); +} diff --git a/app/(tabs)/profile.tsx b/app/(tabs)/profile.tsx new file mode 100644 index 0000000..a9403c4 --- /dev/null +++ b/app/(tabs)/profile.tsx @@ -0,0 +1,10 @@ +import { TabPlaceholderScreen } from "@/components/tab-placeholder-screen"; + +export default function ProfileScreen() { + return ( + + ); +} diff --git a/app/language-selection.tsx b/app/LanguageSelection.tsx similarity index 92% rename from app/language-selection.tsx rename to app/LanguageSelection.tsx index bfda703..fa40c9a 100644 --- a/app/language-selection.tsx +++ b/app/LanguageSelection.tsx @@ -1,5 +1,6 @@ import { images } from "@/constants/images"; import { defaultLanguageId, languages } from "@/data/languages"; +import { useLanguageStore } from "@/store/UseLanguageStore"; import type { SupportedLanguage } from "@/types/learning"; import { Ionicons } from "@expo/vector-icons"; import { router } from "expo-router"; @@ -19,8 +20,14 @@ import { useSafeAreaInsets } from "react-native-safe-area-context"; export default function LanguageSelectionScreen() { const insets = useSafeAreaInsets(); const { width } = useWindowDimensions(); + const persistedLanguageId = useLanguageStore((state) => state.selectedLanguageId); + const setSelectedLanguage = useLanguageStore( + (state) => state.setSelectedLanguageId, + ); const [searchQuery, setSearchQuery] = useState(""); - const [selectedLanguageId, setSelectedLanguageId] = useState(defaultLanguageId); + const [selectedLanguageId, setSelectedLanguageId] = useState( + persistedLanguageId ?? defaultLanguageId, + ); const earthWidth = Math.min(width * 0.95, 520); const earthHeight = earthWidth * (828 / 1127); @@ -51,6 +58,15 @@ export default function LanguageSelectionScreen() { [selectedLanguageId], ); + const handleConfirmLanguage = () => { + if (!selectedLanguage) { + return; + } + + setSelectedLanguage(selectedLanguage.id); + router.replace("/"); + }; + return ( [styles.confirmButton, pressed && styles.pressed]} > diff --git a/app/index.tsx b/app/index.tsx index f7bb34a..ed85f51 100644 --- a/app/index.tsx +++ b/app/index.tsx @@ -1,51 +1,13 @@ +import { useLanguageStore } from "@/store/UseLanguageStore"; import { useAuth } from "@clerk/expo"; -import { images } from "@/constants/images"; -import { colors, typography } from "@/theme/tokens"; -import { Link, Redirect } from "expo-router"; -import type React from "react"; -import { - Image, - ScrollView, - StyleSheet, - Text, - View, - useWindowDimensions, -} from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -const primaryColors = [ - { name: "Lingua Purple", hex: colors.primary.purple }, - { name: "Lingua Deep Purple", hex: colors.primary.deepPurple }, - { name: "Lingua Blue", hex: colors.primary.blue }, - { name: "Lingua Green", hex: colors.primary.green }, -] as const; - -const semanticColors = [ - { name: "Success", hex: colors.semantic.success }, - { name: "Warning", hex: colors.semantic.warning }, - { name: "Streak", hex: colors.semantic.streak }, - { name: "Error", hex: colors.semantic.error }, - { name: "Info", hex: colors.semantic.info }, -] as const; - -const neutralColors = [ - { name: "Text / Primary", hex: colors.neutral.textPrimary }, - { name: "Text / Secondary", hex: colors.neutral.textSecondary }, - { name: "Border", hex: colors.neutral.border }, - { name: "Surface", hex: colors.neutral.surface }, - { name: "Background", hex: colors.neutral.background }, -] as const; - -const typeRows = Object.values(typography.scale); +import { Redirect } from "expo-router"; export default function Index() { const { isLoaded, isSignedIn } = useAuth(); - const insets = useSafeAreaInsets(); - const { width } = useWindowDimensions(); - const isWide = width >= 900; - const horizontalPadding = isWide ? 28 : 16; + const hasHydratedLanguage = useLanguageStore((state) => state.hasHydrated); + const selectedLanguageId = useLanguageStore((state) => state.selectedLanguageId); - if (!isLoaded) { + if (!isLoaded || !hasHydratedLanguage) { return null; } @@ -53,239 +15,9 @@ export default function Index() { return ; } - return ( - - - - - - Open Profile - - - - - - Open Onboarding - - - - - - Choose Language - - - - - - - - lingua - - - - - - - - - - - - - - Font Family - - Poppins - - - Poppins is a modern, geometric sans-serif typeface that provides - excellent readability and a friendly personality. - - - - {typeRows.map((row) => ( - - ))} - - - - - - ); -} - -type DesignCardProps = { - children: React.ReactNode; - fill?: boolean; - title: string; -}; - -function DesignCard({ children, fill, title }: DesignCardProps) { - return ( - - - {title} - - - {children} - - ); -} - -type ColorItem = { - hex: string; - name: string; -}; - -type ColorGroupProps = { - items: readonly ColorItem[]; - outlinedLast?: boolean; - size: "large" | "medium"; - title: string; -}; - -function ColorGroup({ items, outlinedLast, size, title }: ColorGroupProps) { - const swatchStyle = - size === "large" ? styles.largeSwatch : styles.mediumSwatch; - - return ( - - {title} - - {items.map((item, index) => { - const isOutlined = Boolean( - outlinedLast && index === items.length - 1, - ); - - return ( - - - - {item.name} - - - {item.hex} - - - ); - })} - - - ); -} - -type TypographyRowProps = { - row: (typeof typeRows)[number]; -}; - -function TypographyRow({ row }: TypographyRowProps) { - const isHeading = row.label.startsWith("H"); - const fontClass = - row.weight === "Bold" - ? "font-poppins-bold" - : row.weight === "SemiBold" - ? "font-poppins-semibold" - : row.weight === "Medium" - ? "font-poppins-medium" - : "font-poppins"; + if (!selectedLanguageId) { + return ; + } - return ( - - - {row.label} - - {row.usage} - {row.size}px - {row.weight} - - {isHeading - ? (row.lineHeight / row.size).toFixed(1) - : row.label === "Caption" - ? "1.4" - : "1.6"} - - - ); + return ; } - -const styles = StyleSheet.create({ - screen: { - backgroundColor: colors.neutral.background, - flex: 1, - }, - content: { - backgroundColor: colors.neutral.background, - }, - contentWide: { - padding: 28, - }, - contentNarrow: { - padding: 16, - }, - column: { - flex: 1, - }, - card: { - shadowColor: colors.neutral.textPrimary, - shadowOffset: { height: 8, width: 0 }, - shadowOpacity: 0.03, - shadowRadius: 18, - }, - fillCard: { - minHeight: 892, - }, - logo: { - height: 112, - width: 112, - }, - largeSwatch: { - borderRadius: 8, - height: 98, - width: 104, - }, - mediumSwatch: { - borderRadius: 8, - height: 78, - width: 78, - }, - typeLabel: { - width: 160, - }, -}); diff --git a/app/profile.tsx b/app/profile.tsx deleted file mode 100644 index 066c3ce..0000000 --- a/app/profile.tsx +++ /dev/null @@ -1,121 +0,0 @@ -import { useAuth, useClerk, useUser } from "@clerk/expo"; -import { Ionicons } from "@expo/vector-icons"; -import { Redirect, router } from "expo-router"; -import { useState } from "react"; -import { Image, Pressable, Text, View } from "react-native"; -import { useSafeAreaInsets } from "react-native-safe-area-context"; - -export default function ProfileScreen() { - const { isLoaded, isSignedIn } = useAuth(); - const { signOut } = useClerk(); - const { user } = useUser(); - const insets = useSafeAreaInsets(); - const [isSigningOut, setSigningOut] = useState(false); - const [signOutError, setSignOutError] = useState(null); - - const emailAddress = user?.primaryEmailAddress?.emailAddress ?? ""; - const displayName = user?.fullName ?? user?.firstName ?? "Language Learner"; - - const handleSignOut = async () => { - if (isSigningOut) { - return; - } - - setSigningOut(true); - setSignOutError(null); - - try { - await signOut(); - router.replace("/onboarding"); - } catch (error) { - console.error("Failed to sign out", error); - setSignOutError("Unable to sign out. Please try again."); - } finally { - setSigningOut(false); - } - }; - - if (!isLoaded) { - return null; - } - - if (!isSignedIn) { - return ; - } - - return ( - - - - router.back()} - > - - - - Profile - - - - - - {user?.imageUrl ? ( - - ) : ( - - - - )} - - - {displayName} - - {emailAddress ? ( - - {emailAddress} - - ) : null} - - - - - - Account - - - Signed in with Clerk - - - - - - - {isSigningOut ? "Signing Out..." : "Sign Out"} - - - {signOutError ? ( - - {signOutError} - - ) : null} - - - - ); -} diff --git a/assets/images/earth-cropped.png b/assets/images/EarthCropped.png similarity index 100% rename from assets/images/earth-cropped.png rename to assets/images/EarthCropped.png diff --git a/components/bottom-tab-bar.tsx b/components/bottom-tab-bar.tsx new file mode 100644 index 0000000..0383565 --- /dev/null +++ b/components/bottom-tab-bar.tsx @@ -0,0 +1,158 @@ +import { Ionicons } from "@expo/vector-icons"; +import type { BottomTabBarProps } from "@react-navigation/bottom-tabs"; +import type { ComponentProps } from "react"; +import { useEffect, useRef, useState } from "react"; +import { + Animated, + Pressable, + StyleSheet, + Text, + View, + type LayoutChangeEvent, +} from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +type TabIconName = ComponentProps["name"]; + +const ACTIVE_SIZE = 54; + +const tabIcons: Record = { + "ai-teacher": "headset", + chat: "chatbubble-outline", + home: "home", + learn: "book-outline", + profile: "person-outline", +}; + +const tabLabels: Record = { + "ai-teacher": "AI Teacher", + chat: "Chat", + home: "Home", + learn: "Learn", + profile: "Profile", +}; + +export function BottomTabBar({ + descriptors, + navigation, + state, +}: BottomTabBarProps) { + const insets = useSafeAreaInsets(); + const [barWidth, setBarWidth] = useState(0); + const indicatorX = useRef(new Animated.Value(0)).current; + const tabWidth = barWidth / state.routes.length; + + useEffect(() => { + if (!barWidth) { + return; + } + + Animated.spring(indicatorX, { + damping: 18, + mass: 0.7, + stiffness: 180, + toValue: state.index * tabWidth + (tabWidth - ACTIVE_SIZE) / 2, + useNativeDriver: true, + }).start(); + }, [barWidth, indicatorX, state.index, tabWidth]); + + const handleLayout = (event: LayoutChangeEvent) => { + setBarWidth(event.nativeEvent.layout.width); + }; + + return ( + + + {barWidth > 0 ? ( + + + + ) : null} + + {state.routes.map((route, index) => { + const descriptor = descriptors[route.key]; + const isFocused = state.index === index; + const label = tabLabels[route.name] ?? String(descriptor.options.title); + const iconName = tabIcons[route.name] ?? "ellipse-outline"; + + const onPress = () => { + const event = navigation.emit({ + canPreventDefault: true, + target: route.key, + type: "tabPress", + }); + + if (!isFocused && !event.defaultPrevented) { + navigation.navigate(route.name, route.params); + } + }; + + return ( + pressed && styles.pressed} + > + {isFocused ? ( + + ) : ( + <> + + + {label} + + + )} + + ); + })} + + + ); +} + +const styles = StyleSheet.create({ + activeCircleContainer: { + alignItems: "center", + backgroundColor: "#5b3bf6", + borderRadius: ACTIVE_SIZE / 2, + height: ACTIVE_SIZE, + justifyContent: "center", + position: "absolute", + top: 10, + width: ACTIVE_SIZE, + }, + activeCircle: { + left: 0, + zIndex: 1, + }, + bar: { + boxShadow: "0 8px 24px rgba(13, 19, 43, 0.08)", + }, + pressed: { + opacity: 0.72, + }, +}); diff --git a/components/tab-placeholder-screen.tsx b/components/tab-placeholder-screen.tsx new file mode 100644 index 0000000..8995b66 --- /dev/null +++ b/components/tab-placeholder-screen.tsx @@ -0,0 +1,35 @@ +import { Text, View } from "react-native"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +type TabPlaceholderScreenProps = { + subtitle: string; + title: string; +}; + +export function TabPlaceholderScreen({ + subtitle, + title, +}: TabPlaceholderScreenProps) { + const insets = useSafeAreaInsets(); + + return ( + + + + + {title} + + + {subtitle} + + + + + ); +} diff --git a/constants/images.ts b/constants/images.ts index 66f3651..5cffbcb 100644 --- a/constants/images.ts +++ b/constants/images.ts @@ -2,7 +2,7 @@ import mascotLogo from "../assets/images/mascot-logo.png"; import mascotAuth from "../assets/images/mascot-auth.png"; import mascotWelcome from "../assets/images/mascot-welcome.png"; import earth from "../assets/images/earth.png"; -import earthCropped from "../assets/images/earth-cropped.png"; +import earthCropped from "../assets/images/EarthCropped.png"; import chineseFlag from "../assets/images/flags/chinese.png"; import englishFlag from "../assets/images/flags/english.png"; import frenchFlag from "../assets/images/flags/french.png"; diff --git a/package-lock.json b/package-lock.json index 84c0289..62e7e35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@clerk/expo": "^3.2.12", "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", @@ -40,7 +41,8 @@ "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", "react-native-worklets": "0.5.1", - "tailwindcss": "^4.3.0" + "tailwindcss": "^4.3.0", + "zustand": "^5.0.13" }, "devDependencies": { "@types/react": "~19.1.0", @@ -1595,6 +1597,35 @@ "zustand": "5.0.3" } }, + "node_modules/@base-org/account/node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "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/@clerk/clerk-js": { "version": "6.11.1", "resolved": "https://registry.npmmirror.com/@clerk/clerk-js/-/clerk-js-6.11.1.tgz", @@ -1652,6 +1683,39 @@ } } }, + "node_modules/@clerk/clerk-js/node_modules/react": { + "version": "19.2.6", + "resolved": "https://registry.npmmirror.com/react/-/react-19.2.6.tgz", + "integrity": "sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==", + "license": "MIT", + "optional": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@clerk/clerk-js/node_modules/react-dom": { + "version": "19.2.6", + "resolved": "https://registry.npmmirror.com/react-dom/-/react-dom-19.2.6.tgz", + "integrity": "sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.6" + } + }, + "node_modules/@clerk/clerk-js/node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmmirror.com/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@clerk/expo": { "version": "3.2.12", "resolved": "https://registry.npmmirror.com/@clerk/expo/-/expo-3.2.12.tgz", @@ -3080,16 +3144,15 @@ } }, "node_modules/@react-native-async-storage/async-storage": { - "version": "1.24.0", - "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", - "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "version": "2.2.0", + "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-2.2.0.tgz", + "integrity": "sha512-gvRvjR5JAaUZF8tv2Kcq/Gbt3JHwbKFYfmb445rhOj6NUMx3qPLixmDx5pZAyb9at1bYvJ4/eTUipU5aki45xw==", "license": "MIT", - "optional": true, "dependencies": { "merge-options": "^3.0.4" }, "peerDependencies": { - "react-native": "^0.0.0-0 || >=0.60 <1.0" + "react-native": "^0.0.0-0 || >=0.65 <1.0" } }, "node_modules/@react-native/assets-registry": { @@ -3568,6 +3631,90 @@ "@react-native-async-storage/async-storage": "^1.17.7" } }, + "node_modules/@solana-mobile/wallet-standard-mobile/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, + "node_modules/@solana/buffer-layout": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/@solana/buffer-layout/-/buffer-layout-4.0.1.tgz", + "integrity": "sha512-E1ImOIAD1tBZFRdjeM4/pzTiTApC0AOBGwyAMS4fwIodCWArzJ3DWdoh8cKxeFM2fElkxBh2Aqts1BPC373rHA==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer": "~6.0.3" + }, + "engines": { + "node": ">=5.10" + } + }, + "node_modules/@solana/buffer-layout/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, "node_modules/@solana/codecs-strings": { "version": "6.9.0", "resolved": "https://registry.npmmirror.com/@solana/codecs-strings/-/codecs-strings-6.9.0.tgz", @@ -3680,6 +3827,49 @@ "node": ">=20" } }, + "node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@solana/errors/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmmirror.com/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@solana/errors/node_modules/commander": { + "version": "14.0.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-14.0.3.tgz", + "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20" + } + }, "node_modules/@solana/wallet-adapter-base": { "version": "0.9.27", "resolved": "https://registry.npmmirror.com/@solana/wallet-adapter-base/-/wallet-adapter-base-0.9.27.tgz", @@ -3740,6 +3930,19 @@ "react-native": ">0.74" } }, + "node_modules/@solana/wallet-adapter-react/node_modules/@solana-mobile/wallet-adapter-mobile/node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmmirror.com/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, "node_modules/@solana/wallet-standard": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/@solana/wallet-standard/-/wallet-standard-1.1.4.tgz", @@ -3860,6 +4063,75 @@ "react": "*" } }, + "node_modules/@solana/web3.js": { + "version": "1.98.4", + "resolved": "https://registry.npmmirror.com/@solana/web3.js/-/web3.js-1.98.4.tgz", + "integrity": "sha512-vv9lfnvjUsRiq//+j5pBdXig0IQdtzA0BRZ3bXEP4KaIyF1CcaydWqgyzQgfZMNIsWNWmG+AUHwPy4AHOD6gpw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/runtime": "^7.25.0", + "@noble/curves": "^1.4.2", + "@noble/hashes": "^1.4.0", + "@solana/buffer-layout": "^4.0.1", + "@solana/codecs-numbers": "^2.1.0", + "agentkeepalive": "^4.5.0", + "bn.js": "^5.2.1", + "borsh": "^0.7.0", + "bs58": "^4.0.1", + "buffer": "6.0.3", + "fast-stable-stringify": "^1.0.0", + "jayson": "^4.1.1", + "node-fetch": "^2.7.0", + "rpc-websockets": "^9.0.2", + "superstruct": "^2.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/@solana/web3.js/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/@solana/web3.js/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/@stripe/stripe-js": { "version": "5.6.0", "resolved": "https://registry.npmmirror.com/@stripe/stripe-js/-/stripe-js-5.6.0.tgz", @@ -4256,6 +4528,16 @@ "@babel/types": "^7.28.2" } }, + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmmirror.com/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmmirror.com/@types/debug/-/debug-4.1.13.tgz", @@ -4344,7 +4626,7 @@ "version": "19.1.17", "resolved": "https://registry.npmmirror.com/@types/react/-/react-19.1.17.tgz", "integrity": "sha512-Qec1E3mhALmaspIrhWt9jkQMNdw6bReVu64mjvhbhq2NFPftLPVr+l1SZgmw/66WwBNpDh7ao5AT6gF5v41PFA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -4356,6 +4638,16 @@ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "7.4.7", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-7.4.7.tgz", + "integrity": "sha512-JQbbmxZTZehdc2iszGKs5oC3NFnjeay7mtAWrdt7qNtAVK0g19muApzAy4bm9byz79xa2ZnO/BOBC2R8RC5Lww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmmirror.com/@types/yargs/-/yargs-17.0.35.tgz", @@ -5163,6 +5455,19 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmmirror.com/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/ajv": { "version": "6.15.0", "resolved": "https://registry.npmmirror.com/ajv/-/ajv-6.15.0.tgz", @@ -5799,6 +6104,45 @@ "node": ">=0.6" } }, + "node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmmirror.com/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT", + "peer": true + }, + "node_modules/borsh": { + "version": "0.7.0", + "resolved": "https://registry.npmmirror.com/borsh/-/borsh-0.7.0.tgz", + "integrity": "sha512-CLCsZGIBCFnPtkNnieW/a8wmreDmfUtjU2m9yHrzPXIlNbqVs0AQrSatSG6vdNYUqdc83tkQi2eHfF98ubzQLA==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "bn.js": "^5.2.0", + "bs58": "^4.0.0", + "text-encoding-utf-8": "^1.0.2" + } + }, + "node_modules/borsh/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmmirror.com/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/borsh/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, "node_modules/bplist-creator": { "version": "0.1.0", "resolved": "https://registry.npmmirror.com/bplist-creator/-/bplist-creator-0.1.0.tgz", @@ -5933,6 +6277,21 @@ "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", "license": "MIT" }, + "node_modules/bufferutil": { + "version": "4.1.0", + "resolved": "https://registry.npmmirror.com/bufferutil/-/bufferutil-4.1.0.tgz", + "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmmirror.com/bytes/-/bytes-3.1.2.tgz", @@ -6394,7 +6753,7 @@ "version": "3.2.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/data-view-buffer": { @@ -6568,6 +6927,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/delay": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/delay/-/delay-5.0.0.tgz", + "integrity": "sha512-ReEBKkIfe4ya47wlPYf/gu5ib6yUG0/Aez0JQZQz94kiWtRQvZIQbTiehsnwHvLSWJnQdhVeqYue7Id1dKr0qw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/depd/-/depd-2.0.0.tgz", @@ -6906,6 +7278,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmmirror.com/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", + "license": "MIT", + "peer": true + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmmirror.com/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "es6-promise": "^4.0.3" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz", @@ -8098,6 +8487,15 @@ "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", "license": "Apache-2.0" }, + "node_modules/eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha512-GipyPsXO1anza0AOZdy69Im7hGFCNB7Y/NGjDlZGJ3GJJLtwNSb2vrzYrTYJRrRloVx7pl+bhUaTB8yiccPvFQ==", + "peer": true, + "engines": { + "node": "> 0.1.90" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmmirror.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -8117,6 +8515,13 @@ "dev": true, "license": "MIT" }, + "node_modules/fast-stable-stringify": { + "version": "1.0.0", + "resolved": "https://registry.npmmirror.com/fast-stable-stringify/-/fast-stable-stringify-1.0.0.tgz", + "integrity": "sha512-wpYMUmFu5f00Sm0cj2pfivpmawLZ0NKdviQ4w9zJeR8JVtOpOxHmLaJuj0vxvGqMJQWyP/COUkF75/57OKyRag==", + "license": "MIT", + "peer": true + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmmirror.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -8821,6 +9226,16 @@ "node": ">= 14" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/hyphenate-style-name": { "version": "1.1.0", "resolved": "https://registry.npmmirror.com/hyphenate-style-name/-/hyphenate-style-name-1.1.0.tgz", @@ -9272,7 +9687,6 @@ "resolved": "https://registry.npmmirror.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz", "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", "license": "MIT", - "optional": true, "engines": { "node": ">=8" } @@ -9447,6 +9861,16 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, + "node_modules/isomorphic-ws": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz", + "integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==", + "license": "MIT", + "peer": true, + "peerDependencies": { + "ws": "*" + } + }, "node_modules/isows": { "version": "1.0.7", "resolved": "https://registry.npmmirror.com/isows/-/isows-1.0.7.tgz", @@ -9505,6 +9929,57 @@ "node": ">= 0.4" } }, + "node_modules/jayson": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/jayson/-/jayson-4.3.0.tgz", + "integrity": "sha512-AauzHcUcqs8OBnCHOkJY280VaTiCm57AbuO7lqzcw7JapGj50BisE3xhksye4zlTSR1+1tAz67wLTl8tEH1obQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/connect": "^3.4.33", + "@types/node": "^12.12.54", + "@types/ws": "^7.4.4", + "commander": "^2.20.3", + "delay": "^5.0.0", + "es6-promisify": "^5.0.0", + "eyes": "^0.1.8", + "isomorphic-ws": "^4.0.1", + "json-stringify-safe": "^5.0.1", + "stream-json": "^1.9.1", + "uuid": "^8.3.2", + "ws": "^7.5.10" + }, + "bin": { + "jayson": "bin/jayson.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/jayson/node_modules/@types/node": { + "version": "12.20.55", + "resolved": "https://registry.npmmirror.com/@types/node/-/node-12.20.55.tgz", + "integrity": "sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==", + "license": "MIT", + "peer": true + }, + "node_modules/jayson/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmmirror.com/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT", + "peer": true + }, + "node_modules/jayson/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmmirror.com/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "peer": true, + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/jest-environment-node": { "version": "29.7.0", "resolved": "https://registry.npmmirror.com/jest-environment-node/-/jest-environment-node-29.7.0.tgz", @@ -9777,6 +10252,13 @@ "dev": true, "license": "MIT" }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==", + "license": "ISC", + "peer": true + }, "node_modules/json5": { "version": "2.2.3", "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", @@ -10305,7 +10787,6 @@ "resolved": "https://registry.npmmirror.com/merge-options/-/merge-options-3.0.4.tgz", "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", "license": "MIT", - "optional": true, "dependencies": { "is-plain-obj": "^2.1.0" }, @@ -10879,6 +11360,19 @@ "node": ">= 6.13.0" } }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmmirror.com/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "peer": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, "node_modules/node-int64": { "version": "0.4.0", "resolved": "https://registry.npmmirror.com/node-int64/-/node-int64-0.4.0.tgz", @@ -12610,6 +13104,103 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/rpc-websockets": { + "version": "9.3.10", + "resolved": "https://registry.npmmirror.com/rpc-websockets/-/rpc-websockets-9.3.10.tgz", + "integrity": "sha512-QT5PQ6LiWhA5RCS93oWwgxU4XzQltkYm8C3aTmmKEgj0HolGRo3VbdzELw7CEV35l9T7Amha8Vnr4rCfSjVP+w==", + "license": "LGPL-3.0-only", + "peer": true, + "dependencies": { + "@swc/helpers": "^0.5.11", + "@types/ws": "^8.2.2", + "buffer": "^6.0.3", + "eventemitter3": "^5.0.1", + "ws": "^8.5.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "paypal", + "url": "https://paypal.me/kozjak" + }, + "optionalDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^6.0.0" + } + }, + "node_modules/rpc-websockets/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/rpc-websockets/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmmirror.com/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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", + "peer": true, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/rpc-websockets/node_modules/utf-8-validate": { + "version": "6.0.6", + "resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-6.0.6.tgz", + "integrity": "sha512-q3l3P9UtEEiAHcsgsqTgf9PPjctrDWoIXW3NpOHFdRDbLvu4DLIcxHangJ4RLrWkBcKjmcs/6NkerI8T/rE4LA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, + "node_modules/rpc-websockets/node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "peer": true, + "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/safe-array-concat": { "version": "1.1.4", "resolved": "https://registry.npmmirror.com/safe-array-concat/-/safe-array-concat-1.1.4.tgz", @@ -13194,6 +13785,23 @@ "node": ">= 0.10.0" } }, + "node_modules/stream-chain": { + "version": "2.2.5", + "resolved": "https://registry.npmmirror.com/stream-chain/-/stream-chain-2.2.5.tgz", + "integrity": "sha512-1TJmBx6aSWqZ4tx7aTpBDXK0/e2hhcNSTV8+CbFJtDjbb+I1mZ8lHit0Grw9GRT+6JbIrrDd8esncgBi8aBXGA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/stream-json": { + "version": "1.9.1", + "resolved": "https://registry.npmmirror.com/stream-json/-/stream-json-1.9.1.tgz", + "integrity": "sha512-uWkjJ+2Nt/LO9Z/JyKZbMusL8Dkh97uUBTv3AJQ74y07lVahLY4eEFsPsE97pxYBwr8nnjMAIch5eqI0gPShyw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "stream-chain": "^2.2.5" + } + }, "node_modules/strict-uri-encode": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", @@ -13393,6 +14001,16 @@ "node": ">= 6" } }, + "node_modules/superstruct": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/superstruct/-/superstruct-2.0.2.tgz", + "integrity": "sha512-uV+TFRZdXsqXTL2pRvujROjdZQ4RAlBUS5BTh9IGm+jTqQntYThciG/qu57Gs69yjnVUSqdxF9YLmSnpupBW9A==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmmirror.com/supports-color/-/supports-color-7.2.0.tgz", @@ -13564,6 +14182,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-encoding-utf-8": { + "version": "1.0.2", + "resolved": "https://registry.npmmirror.com/text-encoding-utf-8/-/text-encoding-utf-8-1.0.2.tgz", + "integrity": "sha512-8bw4MY9WjdsD2aMtO0OzOCY3pXGYNx2d2FfHRVUKkiCPDWjKuOlhLVASS+pD7VkLTVjW268LYJHwsnPFlBpbAg==", + "peer": true + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmmirror.com/thenify/-/thenify-3.3.1.tgz", @@ -13804,7 +14428,6 @@ "version": "5.9.3", "resolved": "https://registry.npmmirror.com/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -14059,6 +14682,21 @@ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, + "node_modules/utf-8-validate": { + "version": "5.0.10", + "resolved": "https://registry.npmmirror.com/utf-8-validate/-/utf-8-validate-5.0.10.tgz", + "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "node-gyp-build": "^4.3.0" + }, + "engines": { + "node": ">=6.14.2" + } + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmmirror.com/utils-merge/-/utils-merge-1.0.1.tgz", @@ -14786,9 +15424,9 @@ } }, "node_modules/zustand": { - "version": "5.0.3", - "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.3.tgz", - "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "version": "5.0.13", + "resolved": "https://registry.npmmirror.com/zustand/-/zustand-5.0.13.tgz", + "integrity": "sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==", "license": "MIT", "engines": { "node": ">=12.20.0" diff --git a/package.json b/package.json index 99e80f9..c9565bd 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "dependencies": { "@clerk/expo": "^3.2.12", "@expo/vector-icons": "^15.0.3", + "@react-native-async-storage/async-storage": "^2.2.0", "@react-navigation/bottom-tabs": "^7.4.0", "@react-navigation/elements": "^2.6.3", "@react-navigation/native": "^7.1.8", @@ -44,7 +45,8 @@ "react-native-screens": "~4.16.0", "react-native-web": "~0.21.0", "react-native-worklets": "0.5.1", - "tailwindcss": "^4.3.0" + "tailwindcss": "^4.3.0", + "zustand": "^5.0.13" }, "devDependencies": { "@types/react": "~19.1.0", diff --git a/store/UseLanguageStore.ts b/store/UseLanguageStore.ts new file mode 100644 index 0000000..51cd7d4 --- /dev/null +++ b/store/UseLanguageStore.ts @@ -0,0 +1,60 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { create } from "zustand"; +import { + createJSONStorage, + persist, + type StateStorage, +} from "zustand/middleware"; + +type LanguageState = { + clearSelectedLanguage: () => void; + hasHydrated: boolean; + selectedLanguageId: string | null; + setHasHydrated: (hasHydrated: boolean) => void; + setSelectedLanguageId: (languageId: string) => void; +}; + +const noopStorage: StateStorage = { + getItem: () => null, + removeItem: () => undefined, + setItem: () => undefined, +}; + +const getLanguageStorage = () => { + if (process.env.EXPO_OS === "web" && typeof window === "undefined") { + return noopStorage; + } + + return AsyncStorage; +}; + +export const useLanguageStore = create()( + persist( + (set) => ({ + clearSelectedLanguage: () => set({ selectedLanguageId: null }), + hasHydrated: false, + selectedLanguageId: null, + setHasHydrated: (hasHydrated) => set({ hasHydrated }), + setSelectedLanguageId: (languageId) => + set({ selectedLanguageId: languageId }), + }), + { + name: "lingua-language-storage", + onRehydrateStorage: () => (state) => { + state?.setHasHydrated(true); + }, + partialize: (state) => ({ + selectedLanguageId: state.selectedLanguageId, + }), + storage: createJSONStorage(getLanguageStorage), + }, + ), +); + +useLanguageStore.persist.onFinishHydration((state) => { + state.setHasHydrated(true); +}); + +if (useLanguageStore.persist.hasHydrated()) { + useLanguageStore.setState({ hasHydrated: true }); +}