diff --git a/frontend/screens/ExtraInfoScreen.tsx b/frontend/app/(tabs)/ExtraInfoScreen.tsx similarity index 100% rename from frontend/screens/ExtraInfoScreen.tsx rename to frontend/app/(tabs)/ExtraInfoScreen.tsx diff --git a/frontend/screens/HomeScreen.tsx b/frontend/app/(tabs)/HomeScreen.tsx similarity index 100% rename from frontend/screens/HomeScreen.tsx rename to frontend/app/(tabs)/HomeScreen.tsx diff --git a/frontend/screens/SignupScreen.tsx b/frontend/app/(tabs)/SignupScreen.tsx similarity index 100% rename from frontend/screens/SignupScreen.tsx rename to frontend/app/(tabs)/SignupScreen.tsx diff --git a/frontend/app/(tabs)/_layout.tsx b/frontend/app/(tabs)/_layout.tsx new file mode 100644 index 0000000..cfbc1e2 --- /dev/null +++ b/frontend/app/(tabs)/_layout.tsx @@ -0,0 +1,45 @@ +import { Tabs } from 'expo-router'; +import React from 'react'; +import { Platform } from 'react-native'; + +import { HapticTab } from '@/components/HapticTab'; +import { IconSymbol } from '@/components/ui/IconSymbol'; +import TabBarBackground from '@/components/ui/TabBarBackground'; +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +export default function TabLayout() { + const colorScheme = useColorScheme(); + + return ( + + , + }} + /> + , + }} + /> + + ); +} diff --git a/frontend/screens/LoginScreen.tsx b/frontend/app/(tabs)/index.tsx similarity index 100% rename from frontend/screens/LoginScreen.tsx rename to frontend/app/(tabs)/index.tsx diff --git a/frontend/app/+not-found.tsx b/frontend/app/+not-found.tsx new file mode 100644 index 0000000..963b04f --- /dev/null +++ b/frontend/app/+not-found.tsx @@ -0,0 +1,32 @@ +import { Link, Stack } from 'expo-router'; +import { StyleSheet } from 'react-native'; + +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; + +export default function NotFoundScreen() { + return ( + <> + + + This screen doesn't exist. + + Go to home screen! + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + padding: 20, + }, + link: { + marginTop: 15, + paddingVertical: 15, + }, +}); diff --git a/frontend/app/_layout.tsx b/frontend/app/_layout.tsx new file mode 100644 index 0000000..bc79549 --- /dev/null +++ b/frontend/app/_layout.tsx @@ -0,0 +1,44 @@ +import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native'; +import { useFonts } from 'expo-font'; +import { Stack } from 'expo-router'; +import * as SplashScreen from 'expo-splash-screen'; +import { StatusBar } from 'expo-status-bar'; +import { useEffect } from 'react'; +import 'react-native-reanimated'; + +import { useColorScheme } from '@/hooks/useColorScheme'; +import { Provider } from 'react-redux'; +import { store } from '@/store'; + +// Prevent the splash screen from auto-hiding before asset loading is complete. +SplashScreen.preventAutoHideAsync(); + +export default function RootLayout() { + const colorScheme = useColorScheme(); + const [loaded] = useFonts({ + SpaceMono: require('../assets/fonts/SpaceMono-Regular.ttf'), + }); + + useEffect(() => { + if (loaded) { + SplashScreen.hideAsync(); + } + }, [loaded]); + + if (!loaded) { + return null; + } + + return ( + + + + + + + + + + + ); +} diff --git a/frontend/app/index.tsx b/frontend/app/index.tsx index 748b940..d1888c5 100644 --- a/frontend/app/index.tsx +++ b/frontend/app/index.tsx @@ -1,10 +1,10 @@ // index.tsx import * as React from 'react'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; -import LoginScreen from '@/screens/LoginScreen'; -import SignupScreen from '@/screens/SignupScreen'; -import HomeScreen from '@/screens/HomeScreen'; -import ExtraInfoScreen from '@/screens/ExtraInfoScreen'; +import LoginScreen from '@/app/(tabs)/LoginScreen'; +import SignupScreen from '@/app/(tabs)/SignupScreen'; +import HomeScreen from '@/app/(tabs)/HomeScreen'; +import ExtraInfoScreen from '@/app/(tabs)/ExtraInfoScreen'; import { store } from '@/store'; import { Provider } from 'react-redux'; diff --git a/frontend/components/Collapsible.tsx b/frontend/components/Collapsible.tsx new file mode 100644 index 0000000..55bff2f --- /dev/null +++ b/frontend/components/Collapsible.tsx @@ -0,0 +1,45 @@ +import { PropsWithChildren, useState } from 'react'; +import { StyleSheet, TouchableOpacity } from 'react-native'; + +import { ThemedText } from '@/components/ThemedText'; +import { ThemedView } from '@/components/ThemedView'; +import { IconSymbol } from '@/components/ui/IconSymbol'; +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +export function Collapsible({ children, title }: PropsWithChildren & { title: string }) { + const [isOpen, setIsOpen] = useState(false); + const theme = useColorScheme() ?? 'light'; + + return ( + + setIsOpen((value) => !value)} + activeOpacity={0.8}> + + + {title} + + {isOpen && {children}} + + ); +} + +const styles = StyleSheet.create({ + heading: { + flexDirection: 'row', + alignItems: 'center', + gap: 6, + }, + content: { + marginTop: 6, + marginLeft: 24, + }, +}); diff --git a/frontend/components/ExternalLink.tsx b/frontend/components/ExternalLink.tsx new file mode 100644 index 0000000..8f05675 --- /dev/null +++ b/frontend/components/ExternalLink.tsx @@ -0,0 +1,24 @@ +import { Link } from 'expo-router'; +import { openBrowserAsync } from 'expo-web-browser'; +import { type ComponentProps } from 'react'; +import { Platform } from 'react-native'; + +type Props = Omit, 'href'> & { href: string }; + +export function ExternalLink({ href, ...rest }: Props) { + return ( + { + if (Platform.OS !== 'web') { + // Prevent the default behavior of linking to the default browser on native. + event.preventDefault(); + // Open the link in an in-app browser. + await openBrowserAsync(href); + } + }} + /> + ); +} diff --git a/frontend/components/HapticTab.tsx b/frontend/components/HapticTab.tsx new file mode 100644 index 0000000..7f3981c --- /dev/null +++ b/frontend/components/HapticTab.tsx @@ -0,0 +1,18 @@ +import { BottomTabBarButtonProps } from '@react-navigation/bottom-tabs'; +import { PlatformPressable } from '@react-navigation/elements'; +import * as Haptics from 'expo-haptics'; + +export function HapticTab(props: BottomTabBarButtonProps) { + return ( + { + if (process.env.EXPO_OS === 'ios') { + // Add a soft haptic feedback when pressing down on the tabs. + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + props.onPressIn?.(ev); + }} + /> + ); +} diff --git a/frontend/components/HelloWave.tsx b/frontend/components/HelloWave.tsx new file mode 100644 index 0000000..9b4bc31 --- /dev/null +++ b/frontend/components/HelloWave.tsx @@ -0,0 +1,40 @@ +import { useEffect } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { + useSharedValue, + useAnimatedStyle, + withTiming, + withRepeat, + withSequence, +} from 'react-native-reanimated'; + +import { ThemedText } from '@/components/ThemedText'; + +export function HelloWave() { + const rotationAnimation = useSharedValue(0); + + useEffect(() => { + rotationAnimation.value = withRepeat( + withSequence(withTiming(25, { duration: 150 }), withTiming(0, { duration: 150 })), + 4 // Run the animation 4 times + ); + }, []); + + const animatedStyle = useAnimatedStyle(() => ({ + transform: [{ rotate: `${rotationAnimation.value}deg` }], + })); + + return ( + + 👋 + + ); +} + +const styles = StyleSheet.create({ + text: { + fontSize: 28, + lineHeight: 32, + marginTop: -6, + }, +}); diff --git a/frontend/components/ParallaxScrollView.tsx b/frontend/components/ParallaxScrollView.tsx new file mode 100644 index 0000000..5df1d75 --- /dev/null +++ b/frontend/components/ParallaxScrollView.tsx @@ -0,0 +1,82 @@ +import type { PropsWithChildren, ReactElement } from 'react'; +import { StyleSheet } from 'react-native'; +import Animated, { + interpolate, + useAnimatedRef, + useAnimatedStyle, + useScrollViewOffset, +} from 'react-native-reanimated'; + +import { ThemedView } from '@/components/ThemedView'; +import { useBottomTabOverflow } from '@/components/ui/TabBarBackground'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +const HEADER_HEIGHT = 250; + +type Props = PropsWithChildren<{ + headerImage: ReactElement; + headerBackgroundColor: { dark: string; light: string }; +}>; + +export default function ParallaxScrollView({ + children, + headerImage, + headerBackgroundColor, +}: Props) { + const colorScheme = useColorScheme() ?? 'light'; + const scrollRef = useAnimatedRef(); + const scrollOffset = useScrollViewOffset(scrollRef); + const bottom = useBottomTabOverflow(); + const headerAnimatedStyle = useAnimatedStyle(() => { + return { + transform: [ + { + translateY: interpolate( + scrollOffset.value, + [-HEADER_HEIGHT, 0, HEADER_HEIGHT], + [-HEADER_HEIGHT / 2, 0, HEADER_HEIGHT * 0.75] + ), + }, + { + scale: interpolate(scrollOffset.value, [-HEADER_HEIGHT, 0, HEADER_HEIGHT], [2, 1, 1]), + }, + ], + }; + }); + + return ( + + + + {headerImage} + + {children} + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + header: { + height: HEADER_HEIGHT, + overflow: 'hidden', + }, + content: { + flex: 1, + padding: 32, + gap: 16, + overflow: 'hidden', + }, +}); diff --git a/frontend/components/ThemedText.tsx b/frontend/components/ThemedText.tsx new file mode 100644 index 0000000..c0e1a78 --- /dev/null +++ b/frontend/components/ThemedText.tsx @@ -0,0 +1,60 @@ +import { Text, type TextProps, StyleSheet } from 'react-native'; + +import { useThemeColor } from '@/hooks/useThemeColor'; + +export type ThemedTextProps = TextProps & { + lightColor?: string; + darkColor?: string; + type?: 'default' | 'title' | 'defaultSemiBold' | 'subtitle' | 'link'; +}; + +export function ThemedText({ + style, + lightColor, + darkColor, + type = 'default', + ...rest +}: ThemedTextProps) { + const color = useThemeColor({ light: lightColor, dark: darkColor }, 'text'); + + return ( + + ); +} + +const styles = StyleSheet.create({ + default: { + fontSize: 16, + lineHeight: 24, + }, + defaultSemiBold: { + fontSize: 16, + lineHeight: 24, + fontWeight: '600', + }, + title: { + fontSize: 32, + fontWeight: 'bold', + lineHeight: 32, + }, + subtitle: { + fontSize: 20, + fontWeight: 'bold', + }, + link: { + lineHeight: 30, + fontSize: 16, + color: '#0a7ea4', + }, +}); diff --git a/frontend/components/ThemedView.tsx b/frontend/components/ThemedView.tsx new file mode 100644 index 0000000..4d2cb09 --- /dev/null +++ b/frontend/components/ThemedView.tsx @@ -0,0 +1,14 @@ +import { View, type ViewProps } from 'react-native'; + +import { useThemeColor } from '@/hooks/useThemeColor'; + +export type ThemedViewProps = ViewProps & { + lightColor?: string; + darkColor?: string; +}; + +export function ThemedView({ style, lightColor, darkColor, ...otherProps }: ThemedViewProps) { + const backgroundColor = useThemeColor({ light: lightColor, dark: darkColor }, 'background'); + + return ; +} diff --git a/frontend/components/ui/IconSymbol.ios.tsx b/frontend/components/ui/IconSymbol.ios.tsx new file mode 100644 index 0000000..9177f4d --- /dev/null +++ b/frontend/components/ui/IconSymbol.ios.tsx @@ -0,0 +1,32 @@ +import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols'; +import { StyleProp, ViewStyle } from 'react-native'; + +export function IconSymbol({ + name, + size = 24, + color, + style, + weight = 'regular', +}: { + name: SymbolViewProps['name']; + size?: number; + color: string; + style?: StyleProp; + weight?: SymbolWeight; +}) { + return ( + + ); +} diff --git a/frontend/components/ui/IconSymbol.tsx b/frontend/components/ui/IconSymbol.tsx new file mode 100644 index 0000000..f1fabd4 --- /dev/null +++ b/frontend/components/ui/IconSymbol.tsx @@ -0,0 +1,43 @@ +// This file is a fallback for using MaterialIcons on Android and web. + +import MaterialIcons from '@expo/vector-icons/MaterialIcons'; +import { SymbolWeight } from 'expo-symbols'; +import React from 'react'; +import { OpaqueColorValue, StyleProp, ViewStyle } from 'react-native'; + +// Add your SFSymbol to MaterialIcons mappings here. +const MAPPING = { + // See MaterialIcons here: https://icons.expo.fyi + // See SF Symbols in the SF Symbols app on Mac. + 'house.fill': 'home', + 'paperplane.fill': 'send', + 'chevron.left.forwardslash.chevron.right': 'code', + 'chevron.right': 'chevron-right', +} as Partial< + Record< + import('expo-symbols').SymbolViewProps['name'], + React.ComponentProps['name'] + > +>; + +export type IconSymbolName = keyof typeof MAPPING; + +/** + * An icon component that uses native SFSymbols on iOS, and MaterialIcons on Android and web. This ensures a consistent look across platforms, and optimal resource usage. + * + * Icon `name`s are based on SFSymbols and require manual mapping to MaterialIcons. + */ +export function IconSymbol({ + name, + size = 24, + color, + style, +}: { + name: IconSymbolName; + size?: number; + color: string | OpaqueColorValue; + style?: StyleProp; + weight?: SymbolWeight; +}) { + return ; +} diff --git a/frontend/components/ui/TabBarBackground.ios.tsx b/frontend/components/ui/TabBarBackground.ios.tsx new file mode 100644 index 0000000..6668e78 --- /dev/null +++ b/frontend/components/ui/TabBarBackground.ios.tsx @@ -0,0 +1,22 @@ +import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs'; +import { BlurView } from 'expo-blur'; +import { StyleSheet } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; + +export default function BlurTabBarBackground() { + return ( + + ); +} + +export function useBottomTabOverflow() { + const tabHeight = useBottomTabBarHeight(); + const { bottom } = useSafeAreaInsets(); + return tabHeight - bottom; +} diff --git a/frontend/components/ui/TabBarBackground.tsx b/frontend/components/ui/TabBarBackground.tsx new file mode 100644 index 0000000..70d1c3c --- /dev/null +++ b/frontend/components/ui/TabBarBackground.tsx @@ -0,0 +1,6 @@ +// This is a shim for web and Android where the tab bar is generally opaque. +export default undefined; + +export function useBottomTabOverflow() { + return 0; +} diff --git a/frontend/constants/Colors.ts b/frontend/constants/Colors.ts new file mode 100644 index 0000000..14e6784 --- /dev/null +++ b/frontend/constants/Colors.ts @@ -0,0 +1,26 @@ +/** + * Below are the colors that are used in the app. The colors are defined in the light and dark mode. + * There are many other ways to style your app. For example, [Nativewind](https://www.nativewind.dev/), [Tamagui](https://tamagui.dev/), [unistyles](https://reactnativeunistyles.vercel.app), etc. + */ + +const tintColorLight = '#0a7ea4'; +const tintColorDark = '#fff'; + +export const Colors = { + light: { + text: '#11181C', + background: '#fff', + tint: tintColorLight, + icon: '#687076', + tabIconDefault: '#687076', + tabIconSelected: tintColorLight, + }, + dark: { + text: '#ECEDEE', + background: '#151718', + tint: tintColorDark, + icon: '#9BA1A6', + tabIconDefault: '#9BA1A6', + tabIconSelected: tintColorDark, + }, +}; diff --git a/frontend/hooks/useColorScheme.ts b/frontend/hooks/useColorScheme.ts new file mode 100644 index 0000000..17e3c63 --- /dev/null +++ b/frontend/hooks/useColorScheme.ts @@ -0,0 +1 @@ +export { useColorScheme } from 'react-native'; diff --git a/frontend/hooks/useColorScheme.web.ts b/frontend/hooks/useColorScheme.web.ts new file mode 100644 index 0000000..7eb1c1b --- /dev/null +++ b/frontend/hooks/useColorScheme.web.ts @@ -0,0 +1,21 @@ +import { useEffect, useState } from 'react'; +import { useColorScheme as useRNColorScheme } from 'react-native'; + +/** + * To support static rendering, this value needs to be re-calculated on the client side for web + */ +export function useColorScheme() { + const [hasHydrated, setHasHydrated] = useState(false); + + useEffect(() => { + setHasHydrated(true); + }, []); + + const colorScheme = useRNColorScheme(); + + if (hasHydrated) { + return colorScheme; + } + + return 'light'; +} diff --git a/frontend/hooks/useThemeColor.ts b/frontend/hooks/useThemeColor.ts new file mode 100644 index 0000000..0608e73 --- /dev/null +++ b/frontend/hooks/useThemeColor.ts @@ -0,0 +1,21 @@ +/** + * Learn more about light and dark modes: + * https://docs.expo.dev/guides/color-schemes/ + */ + +import { Colors } from '@/constants/Colors'; +import { useColorScheme } from '@/hooks/useColorScheme'; + +export function useThemeColor( + props: { light?: string; dark?: string }, + colorName: keyof typeof Colors.light & keyof typeof Colors.dark +) { + const theme = useColorScheme() ?? 'light'; + const colorFromProps = props[theme]; + + if (colorFromProps) { + return colorFromProps; + } else { + return Colors[theme][colorName]; + } +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index c293442..fab31cb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -29,6 +29,7 @@ "expo-secure-store": "~14.0.0", "expo-splash-screen": "~0.29.13", "expo-status-bar": "~2.0.0", + "expo-symbols": "^0.2.0", "expo-system-ui": "~4.0.5", "expo-web-browser": "~14.0.1", "react": "18.3.1", @@ -6236,148 +6237,14 @@ } }, "node_modules/babel-plugin-react-compiler": { - "version": "0.0.0-experimental-592953e-20240517", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz", - "integrity": "sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@babel/generator": "7.2.0", - "@babel/types": "^7.19.0", - "chalk": "4", - "invariant": "^2.2.4", - "pretty-format": "^24", - "zod": "^3.22.4", - "zod-validation-error": "^2.1.0" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@babel/generator": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", - "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.2.0", - "jsesc": "^2.5.1", - "lodash": "^4.17.10", - "source-map": "^0.5.0", - "trim-right": "^1.0.1" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@jest/types": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", - "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^1.1.1", - "@types/yargs": "^13.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@types/istanbul-reports": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", - "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/istanbul-lib-coverage": "*", - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/@types/yargs": { - "version": "13.0.12", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", - "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "devOptional": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/babel-plugin-react-compiler/node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "devOptional": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/pretty-format": { - "version": "24.9.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", - "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", - "devOptional": true, + "version": "19.0.0-beta-df7b47d-20241124", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-19.0.0-beta-df7b47d-20241124.tgz", + "integrity": "sha512-93iSASR20HNsotcOTQ+KPL0zpgfRFVWL86AtXpmHp995HuMVnC9femd8Winr3GxkPEh8lEOyaw3nqY4q2HUm5w==", "license": "MIT", + "optional": true, + "peer": true, "dependencies": { - "@jest/types": "^24.9.0", - "ansi-regex": "^4.0.0", - "ansi-styles": "^3.2.0", - "react-is": "^16.8.4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/babel-plugin-react-compiler/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", - "devOptional": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" + "@babel/types": "^7.19.0" } }, "node_modules/babel-plugin-react-native-web": { @@ -9855,6 +9722,20 @@ "@babel/highlight": "^7.10.4" } }, + "node_modules/expo-module-scripts/node_modules/@babel/generator": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.2.0.tgz", + "integrity": "sha512-BA75MVfRlFQG2EZgFYIwyT1r6xSkwfP2bdkY/kLZusEYWiJs4xCowab/alaEaT0wSvmVuXGqiefeBlP+7V1yKg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.2.0", + "jsesc": "^2.5.1", + "lodash": "^4.17.10", + "source-map": "^0.5.0", + "trim-right": "^1.0.1" + } + }, "node_modules/expo-module-scripts/node_modules/@expo/config": { "version": "9.0.4", "resolved": "https://registry.npmjs.org/@expo/config/-/config-9.0.4.tgz", @@ -9974,6 +9855,21 @@ "xmlbuilder": "^14.0.0" } }, + "node_modules/expo-module-scripts/node_modules/@jest/types": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-24.9.0.tgz", + "integrity": "sha512-XKK7ze1apu5JWQ5eZjHITP66AX+QsLlbaJRBGYr8pNzwcAE2JVkwnf0yqjHTsDRcjR0mujy/NmZMXw5kl+kGBw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "^2.0.0", + "@types/istanbul-reports": "^1.1.1", + "@types/yargs": "^13.0.0" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/expo-module-scripts/node_modules/@react-native/babel-plugin-codegen": { "version": "0.74.87", "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.74.87.tgz", @@ -10067,6 +9963,66 @@ "@babel/preset-env": "^7.1.6" } }, + "node_modules/expo-module-scripts/node_modules/@types/istanbul-reports": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-1.1.2.tgz", + "integrity": "sha512-P/W9yOX/3oPZSpaYOCQzGqgCQRXn0FFO/V8bWrCQs+wLmvVVxk6CRBXALEvNs9OHIatlnlFokfhuDo2ug01ciw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/istanbul-lib-coverage": "*", + "@types/istanbul-lib-report": "*" + } + }, + "node_modules/expo-module-scripts/node_modules/@types/yargs": { + "version": "13.0.12", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-13.0.12.tgz", + "integrity": "sha512-qCxJE1qgz2y0hA4pIxjBR+PelCH0U5CK1XJXFwCNqfmliatKp47UCXXE9Dyk1OXBDLvsCF57TqQEJaeLfDYEOQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/yargs-parser": "*" + } + }, + "node_modules/expo-module-scripts/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expo-module-scripts/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/expo-module-scripts/node_modules/babel-plugin-react-compiler": { + "version": "0.0.0-experimental-592953e-20240517", + "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-0.0.0-experimental-592953e-20240517.tgz", + "integrity": "sha512-OjG1SVaeQZaJrqkMFJatg8W/MTow8Ak5rx2SI0ETQBO1XvOk/XZGMbltNCPdFJLKghBYoBjC+Y3Ap/Xr7B01mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/generator": "7.2.0", + "@babel/types": "^7.19.0", + "chalk": "4", + "invariant": "^2.2.4", + "pretty-format": "^24", + "zod": "^3.22.4", + "zod-validation-error": "^2.1.0" + } + }, "node_modules/expo-module-scripts/node_modules/babel-preset-expo": { "version": "11.0.15", "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-11.0.15.tgz", @@ -10097,6 +10053,23 @@ "concat-map": "0.0.1" } }, + "node_modules/expo-module-scripts/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/expo-module-scripts/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true, + "license": "MIT" + }, "node_modules/expo-module-scripts/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -10160,6 +10133,19 @@ "jest": "bin/jest.js" } }, + "node_modules/expo-module-scripts/node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/expo-module-scripts/node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -10173,6 +10159,22 @@ "node": "*" } }, + "node_modules/expo-module-scripts/node_modules/pretty-format": { + "version": "24.9.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-24.9.0.tgz", + "integrity": "sha512-00ZMZUiHaJrNfk33guavqgvfJS30sLYf0f8+Srklv0AMPodGGHcoHgksZ3OThYnIvOd+8yMCn0YiEOogjlgsnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "^24.9.0", + "ansi-regex": "^4.0.0", + "ansi-styles": "^3.2.0", + "react-is": "^16.8.4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/expo-module-scripts/node_modules/semver": { "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", @@ -10186,6 +10188,16 @@ "node": ">=10" } }, + "node_modules/expo-module-scripts/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/expo-module-scripts/node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -10521,6 +10533,18 @@ "react-native": "*" } }, + "node_modules/expo-symbols": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-0.2.0.tgz", + "integrity": "sha512-9ci+JBc03e3UvRcdal219FYg5ot7oFWMuyrUwIqI47IoIDUijKn10iuT7T6RjpLtBwHgGvKUK4tue/CiJ+8KeQ==", + "license": "MIT", + "dependencies": { + "sf-symbols-typescript": "^2.0.0" + }, + "peerDependencies": { + "expo": "*" + } + }, "node_modules/expo-system-ui": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-4.0.5.tgz", @@ -17563,6 +17587,15 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sf-symbols-typescript": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/sf-symbols-typescript/-/sf-symbols-typescript-2.0.0.tgz", + "integrity": "sha512-Fc8Uhhl2plqXMw7GQ8q83t/zj1xhNCJvteDNJUDULaH/4a/Eqw5aW1UYEznyEIgkokw7QYXuQ9hOw8jhBLXL0A==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -18793,7 +18826,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -20055,7 +20088,7 @@ "version": "3.23.8", "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", - "devOptional": true, + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/colinhacks" @@ -20065,7 +20098,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-2.1.0.tgz", "integrity": "sha512-VJh93e2wb4c3tWtGgTa0OF/dTt/zoPCPzXq4V11ZjxmEAFaPi/Zss1xIZdEB5RD8GD00U0/iVXgqkF77RV7pdQ==", - "devOptional": true, + "dev": true, "license": "MIT", "engines": { "node": ">=18.0.0" diff --git a/frontend/package.json b/frontend/package.json index c17d4de..7f9704e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -36,6 +36,7 @@ "expo-secure-store": "~14.0.0", "expo-splash-screen": "~0.29.13", "expo-status-bar": "~2.0.0", + "expo-symbols": "^0.2.0", "expo-system-ui": "~4.0.5", "expo-web-browser": "~14.0.1", "react": "18.3.1",