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",