diff --git a/.yarn/install-state.gz b/.yarn/install-state.gz deleted file mode 100644 index d2bd6da..0000000 Binary files a/.yarn/install-state.gz and /dev/null differ diff --git a/App.tsx b/App.tsx index 0d4be86..a92f103 100644 --- a/App.tsx +++ b/App.tsx @@ -8,10 +8,13 @@ import Home from "./screens/Home"; import * as Linking from "expo-linking"; import Login from "./screens/Login"; import { createStackNavigator } from "@react-navigation/stack"; -import store from "./redux/store"; +import store, { RootState } from "./redux/store"; import { Provider, useDispatch } from 'react-redux'; -import { setToken, clearTokens, AuthActionTypes } from './redux/actions'; +import { setToken, clearTokens, AuthActionTypes, setRoles } from './redux/actions'; import { Dispatch } from "@reduxjs/toolkit"; +import { decodeToken } from "./api/decodeToken"; +import { useAppSelector } from "./redux/hooks"; +import { State } from "react-native-gesture-handler"; const prefix = Linking.createURL("/"); console.log(prefix); @@ -39,8 +42,12 @@ const App = () => { console.log("handling deep link:", event.url); const searchParams = new URL(event.url).searchParams; const token = searchParams.get('token'); - console.log("TOKEN", token); - dispatch(setToken(token)) + if (token) { + dispatch(setToken(token)) + const decoded = decodeToken(token); + console.log(decoded.roles); + dispatch(setRoles(decoded.roles)); + } } async function getInitialURL() { @@ -49,6 +56,7 @@ const App = () => { if (initialURL) handleDeepLink({url: initialURL}); } + getInitialURL(); const listener = Linking.addEventListener("url", handleDeepLink); diff --git a/Components/Buttons.tsx b/Components/Buttons.tsx index 5c7344e..f1a25ae 100644 --- a/Components/Buttons.tsx +++ b/Components/Buttons.tsx @@ -2,6 +2,7 @@ import React from "react"; import { Pressable } from "@gluestack-ui/themed"; import { Button } from "@gluestack-ui/themed"; import { styled } from "@gluestack-style/react"; +import Colors from "../constants/Colors"; const StyledButton = styled(Button, { @@ -12,6 +13,9 @@ const StyledButton = styled(Button, width: 250, marginBottom: "$3", bg: "$lightgray", + }, + scan: { + bg: Colors.DARK_BLUE } }, }, diff --git a/Components/EventDropdown.tsx b/Components/EventDropdown.tsx new file mode 100644 index 0000000..08c1b28 --- /dev/null +++ b/Components/EventDropdown.tsx @@ -0,0 +1,100 @@ +import React, { useState, useEffect } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; +import RNPickerSelect from 'react-native-picker-select'; +import { getEvents } from '../api/getEvents'; +import Colors from '../constants/Colors'; + +interface Event { + eventId: string; + name: string; +} + +interface EventDropdownProps { + token: string; + onEventSelect: (eventId: string | null) => void; +} + +const EventDropdown: React.FC = ({ token, onEventSelect }) => { + const [events, setEvents] = useState([]); + const [selectedEvent, setSelectedEvent] = useState(null); + + useEffect(() => { + const fetchEvents = async () => { + try { + const eventData = await getEvents(token); + setEvents(eventData); + } catch (error) { + console.error('Error fetching events:', error); + } + }; + + fetchEvents(); + }, [token]); + + return ( + + { + setSelectedEvent(value); + onEventSelect(value); + }} + items={events.map((event) => ({ + label: event.name, + value: event.eventId, + }))} + style={pickerSelectStyles} + placeholder={{ label: 'Select an event...', value: null}} + /> + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + alignItems: 'center', + padding: 25, + paddingRight: -10 + }, + label: { + fontSize: 18, + marginBottom: 10, + color: Colors.WHITE + }, + selectedEvent: { + marginTop: 20, + fontSize: 16, + color: Colors.WHITE, + }, +}); + +const pickerSelectStyles = StyleSheet.create({ + inputIOS: { + textAlign: 'center', + fontSize: 20, + fontWeight: "bold", + paddingVertical: 12, + paddingHorizontal: 12, + borderWidth: 3, + borderColor: Colors.YELLOW, + backgroundColor: Colors.DARK_BLUE, + borderRadius: 6, + color: Colors.WHITE, + fontFamily: "Inter_500Medium", + paddingRight: 12, // to ensure the text is never behind the icon + + }, + inputAndroid: { + fontSize: 20, + paddingHorizontal: 10, + paddingVertical: 8, + borderWidth: 0.5, + borderColor: 'purple', + borderRadius: 8, + color: Colors.WHITE, + paddingRight: 12, // to ensure the text is never behind the icon + }, +}); + +export default EventDropdown; \ No newline at end of file diff --git a/Components/FoodWaveSVG.tsx b/Components/FoodWaveSVG.tsx new file mode 100644 index 0000000..e1d90ce --- /dev/null +++ b/Components/FoodWaveSVG.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { View, Text } from 'react-native' +import { StyledText } from './Text'; + +import Foodwave1 from '../assets/SVGs/foodwave/food_wave.svg' +import Foodwave2 from '../assets/SVGs/foodwave/food_wave2.svg' +import Foodwave3 from '../assets/SVGs/foodwave/food_wave3.svg' +import Foodwave4 from '../assets/SVGs/foodwave/food_wave4.svg' + + +interface FoodWaveSVGProps { + foodWave: Number; // Ensure you define all props your component expects. + } + +const FoodWaveSVG: React.FC = ({ foodWave }) => { + const getSVG = () => { + switch(foodWave) { + case 1: + return ; + case 2: + return ; + case 3: + return ; + case 4: + return ; + default: + return No SVG available + } + }; + + return ( + + {getSVG()} + + {`Food Wave: ${foodWave}`} + + + ); +}; + +export default FoodWaveSVG; \ No newline at end of file diff --git a/Components/Images.tsx b/Components/Images.tsx index 98a3027..3dbf710 100644 --- a/Components/Images.tsx +++ b/Components/Images.tsx @@ -9,13 +9,13 @@ const Images = styled(Image, qrCode: { height: 300, width: 300, - borderRadius: "$xl", + borderRadius: "$lg", marginTop: "$5", }, loginLogo: { height: 300, width: 300, - borderRadius: "$xl", + borderRadius: "$lg", marginTop: "$5", } } diff --git a/Components/Text.tsx b/Components/Text.tsx index dc4d2ea..4b49f9a 100644 --- a/Components/Text.tsx +++ b/Components/Text.tsx @@ -1,6 +1,6 @@ import { styled } from "@gluestack-style/react"; import { Text } from "@gluestack-ui/themed"; - +import { useFonts, Kufam_400Regular, Kufam_700Bold, Kufam_700Bold_Italic } from "@expo-google-fonts/kufam"; const StyledText = styled(Text, { @@ -11,7 +11,7 @@ const StyledText = styled(Text, fontWeight: "$bold", textAlign: 'center' }, - bigText: { + profileText: { fontSize: 60, fontWeight: "$bold", textAlign: 'center', diff --git a/api/decodeToken.tsx b/api/decodeToken.tsx new file mode 100644 index 0000000..3ce7a80 --- /dev/null +++ b/api/decodeToken.tsx @@ -0,0 +1,16 @@ +import {jwtDecode} from 'jwt-decode'; + +interface DecodedToken { + userId: string; + displayName: string; + roles: string[]; +} + +export function decodeToken(token: string): DecodedToken { + try { + const decoded = jwtDecode(token) + return decoded; + } catch (error) { + console.error('Error decoding token:', error) + } +} \ No newline at end of file diff --git a/api/getAttendee.tsx b/api/getAttendee.tsx index 04aed60..1919e41 100644 --- a/api/getAttendee.tsx +++ b/api/getAttendee.tsx @@ -6,7 +6,7 @@ export const getAttendee = (token: string) => { try { const response = await fetch('https://api.reflectionsprojections.org/attendee/', { headers: { - 'Authorization': `Bearer ${token}`, + Authorization: token }, }); @@ -17,7 +17,6 @@ export const getAttendee = (token: string) => { } const data = await response.json(); - console.log("attendee:", data); dispatch(setAttendee(data)); } catch (error) { console.error('Error fetching attendee:', error); diff --git a/api/getEvents.tsx b/api/getEvents.tsx new file mode 100644 index 0000000..7882399 --- /dev/null +++ b/api/getEvents.tsx @@ -0,0 +1,15 @@ +import axios from "axios"; + +export const getEvents = async (token: string) => { + try{ + const response = await axios.get('https://api.reflectionsprojections.org/events/', { + headers: { + Authorization: token + } + }); + return response.data; + } catch (error) { + console.error("Error in catching events:", error); + throw error; + } +} \ No newline at end of file diff --git a/api/getQRCode.tsx b/api/getQRCode.tsx index 93af4e9..f7448aa 100644 --- a/api/getQRCode.tsx +++ b/api/getQRCode.tsx @@ -5,23 +5,12 @@ export const getQRCode = (token: string) => { try { const response = await fetch('https://api.reflectionsprojections.org/attendee/qr/', { headers: { - 'Authorization': `${token}`, + Authorization: token, 'Content-Type': 'application/json' }, }); const data = await response.json(); - dispatch(setQRCode(data)); - - // Refresh the QR Code every 20 seconds - setInterval(async () => { - const refreshResponse = await fetch('https://api.reflectionsprojections.org/attendee/qr/', { - headers: { - 'Authorization': `${token}`, - }, - }); - const refreshData = await refreshResponse.json(); - dispatch(setQRCode(refreshData)); - }, 20000) + dispatch(setQRCode(data.qrCode)); } catch (error) { console.error('Error fetching qrcode:', error); } diff --git a/api/postCheckIn.tsx b/api/postCheckIn.tsx new file mode 100644 index 0000000..2ee236e --- /dev/null +++ b/api/postCheckIn.tsx @@ -0,0 +1,17 @@ +import axios from "axios"; + +export const postCheckIn = async(token, eventId, qrCode) => { + const payload = { eventId, qrCode } + try { + const response = await axios.post('https://api.reflectionsprojections.org/staff/scan/', payload, { + headers: { + Authorization: token, + 'Content-Type': 'application/json' + } + }); + console.log('post CheckIn:', response.data) + } catch (error) { + console.log('Error posting check in:', error) + alert(`Error with scanning QR Code! Please double check selected event`); + } +}; \ No newline at end of file diff --git a/assets/SVGs/foodwave/food_wave.svg b/assets/SVGs/foodwave/food_wave.svg new file mode 100644 index 0000000..682f62e --- /dev/null +++ b/assets/SVGs/foodwave/food_wave.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/SVGs/foodwave/food_wave2.svg b/assets/SVGs/foodwave/food_wave2.svg new file mode 100644 index 0000000..a8bbe16 --- /dev/null +++ b/assets/SVGs/foodwave/food_wave2.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/SVGs/foodwave/food_wave3.svg b/assets/SVGs/foodwave/food_wave3.svg new file mode 100644 index 0000000..e72bdc7 --- /dev/null +++ b/assets/SVGs/foodwave/food_wave3.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/SVGs/foodwave/food_wave4.svg b/assets/SVGs/foodwave/food_wave4.svg new file mode 100644 index 0000000..a333810 --- /dev/null +++ b/assets/SVGs/foodwave/food_wave4.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/assets/LoginScreen.svg b/assets/SVGs/login/LoginScreen.svg similarity index 100% rename from assets/LoginScreen.svg rename to assets/SVGs/login/LoginScreen.svg diff --git a/assets/SVGs/login/loginPage.svg b/assets/SVGs/login/loginPage.svg new file mode 100644 index 0000000..c512ded --- /dev/null +++ b/assets/SVGs/login/loginPage.svg @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/SVGs/qrcode/qr_frame.svg b/assets/SVGs/qrcode/qr_frame.svg new file mode 100644 index 0000000..a8669dc --- /dev/null +++ b/assets/SVGs/qrcode/qr_frame.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/navigation/Navigation.tsx b/navigation/Navigation.tsx index 17f27c8..7630a44 100644 --- a/navigation/Navigation.tsx +++ b/navigation/Navigation.tsx @@ -9,8 +9,11 @@ import CameraScanner from "../screens/CameraScanner"; import Shop from "../screens/Shop"; import Profile from "../screens/Profile"; import Colors from "../constants/Colors"; +import { useAppSelector } from "../redux/hooks"; +import { RootState } from "../redux/store"; import EventDaysNavigator from "./EventDaysNavigator"; // Adjust path as necessary +import AdminScanner from "../screens/AdminScanner"; const ACTIVE_COLOR = Colors.YELLOW; const INACTIVE_COLOR = Colors.WHITE; @@ -18,6 +21,7 @@ const INACTIVE_COLOR = Colors.WHITE; const AppNavigator: React.FC = () => { // const Stack = createNativeStackNavigator(); const Tab = createBottomTabNavigator(); + const roles = useAppSelector((state: RootState) => state.roles); return ( { solid /> ); + case "AdminScanner": + return ( + + ); case "Shop": return ( { component={Events} options={{ tabBarLabel: () => null }} /> - null }} + /> + ) : ( + null }} /> + )} ({ @@ -37,6 +43,11 @@ export const setToken = (token: string): AuthActionTypes => ({ payload: token, }); +export const setRoles = (roles: string[]): AuthActionTypes => ({ + type: SET_ROLES, + payload: roles, +}); + export const setAttendee = (attendee: Attendee): AuthActionTypes => ({ type: SET_ATTENDEE, payload: attendee, diff --git a/redux/state.ts b/redux/state.ts index 85f9c8b..3dfafd9 100644 --- a/redux/state.ts +++ b/redux/state.ts @@ -1,9 +1,10 @@ import { State } from './types'; -import { AuthActionTypes, SET_TOKEN, SET_ATTENDEE, SET_QRCODE, LOGOUT } from './actions'; +import { AuthActionTypes, SET_TOKEN, SET_ROLES, SET_ATTENDEE, SET_QRCODE, LOGOUT } from './actions'; const initialState: State = { user_id: null, token: null, + roles: null, attendee: null, qrCodeURL: null, isAuthenticated: false, @@ -17,6 +18,11 @@ function stateReducer(state = initialState, action: AuthActionTypes): State { token: action.payload, isAuthenticated: true, }; + case SET_ROLES: + return { + ...state, + roles: action.payload, + }; case SET_ATTENDEE: return { ...state, diff --git a/redux/types.ts b/redux/types.ts index 31d6825..a3411e3 100644 --- a/redux/types.ts +++ b/redux/types.ts @@ -1,6 +1,7 @@ export interface State { user_id: string | null; token: string | null; + roles: string[] | null; isAuthenticated: boolean; attendee: Attendee | null; qrCodeURL: string | null diff --git a/screens/AdminScanner.tsx b/screens/AdminScanner.tsx new file mode 100644 index 0000000..fc70a17 --- /dev/null +++ b/screens/AdminScanner.tsx @@ -0,0 +1,105 @@ +import React, {useEffect, useState} from "react"; +import { + Box, Button, ButtonText, ButtonIcon, View, ExternalLinkIcon +} from "@gluestack-ui/themed"; +import {CameraView, useCameraPermissions} from "expo-camera"; +import {useIsFocused} from "@react-navigation/native"; +import {Linking, StyleSheet} from "react-native"; +import EventDropdown from "../Components/EventDropdown"; +import { useAppSelector } from "../redux/hooks"; +import { RootState } from "../redux/store"; +import { postCheckIn } from "../api/postCheckIn"; +import { StyledButton } from "../Components/Buttons"; +import axios from "axios"; +import Colors from "../constants/Colors"; + +// import { Button, ButtonText, ButtonIcon, AddIcon } from "@gluestack-ui/themed"; + +const AdminScanner: React.FC = () => { + const [status, requestPermission] = useCameraPermissions(); + const [hasPermission, setHasPermission] = useState(false); + const [selectedEvent, setSelectedEvent] = useState(null); + const [scanned, setScanned] = useState(false); + const isFocused = useIsFocused(); + const token = useAppSelector((state: RootState) => state.token); + + useEffect(() => { + requestPermission().then((e) => { + setHasPermission(e?.granted); + }); + }, []); + + const handleBarCodeScanned = ({type, data}) => { + setScanned(true); + postCheckIn(token, selectedEvent, data) + }; + + // mute={true} is used to mute the camera so we dont need mic permissions + // https://github.com/expo/expo/issues/27984 of course it is undocumented <333 + + return ( + + {isFocused && hasPermission ? ( + + {token && ( + + + + )} + + + {scanned && + setScanned(false)} + > + Tap to Scan Again + + } + + + ) : ( + + + )} + + ); +}; + +const styles = StyleSheet.create({ + cameraContainer: { + width: '100%', + height: '100%', + position: 'relative', + }, + dropdownContainer: { + position: 'absolute', + top: 35, + right: 20, + zIndex: 1000 + }, + buttonContainer: { + position: 'absolute', + justifyContent: 'center', + alignContent: 'center', + alignSelf: 'center', + bottom: 20, + zIndex: 1000, + width: '75%' + } + }); + +export default AdminScanner; \ No newline at end of file diff --git a/screens/Login.tsx b/screens/Login.tsx index 981cc2f..623b265 100644 --- a/screens/Login.tsx +++ b/screens/Login.tsx @@ -13,7 +13,7 @@ import { useAppSelector } from "../redux/hooks"; import { RootState } from "../redux/store"; import Colors from "../constants/Colors"; -import ScreenImage from "../assets/LoginScreen.svg"; +import ScreenImage from "../assets/SVGs/login/LoginScreen.svg"; const authUrl = "https://api.reflectionsprojections.org/auth/login/mobile/"; const redirectURL = "reflectionsprojections://Main"; diff --git a/screens/Profile.tsx b/screens/Profile.tsx index 337a076..40796ce 100644 --- a/screens/Profile.tsx +++ b/screens/Profile.tsx @@ -13,64 +13,62 @@ import { getQRCode } from "../api/getQRCode"; import axios from "axios"; import { Attendee } from "../redux/types"; import Colors from "../constants/Colors"; +import { useFonts, Kufam_400Regular, Kufam_700Bold, Kufam_700Bold_Italic } from "@expo-google-fonts/kufam"; +import FoodWaveSVG from "../Components/FoodWaveSVG" + +import QRFrame from '../assets/SVGs/qrcode/qr_frame.svg' const Profile: React.FC = () => { + + const dispatch = useAppDispatch(); + const token = useAppSelector((state: RootState) => state.token); + console.log("profile token:", token); + const attendee = useAppSelector((state: RootState) => state.attendee); + const qrcode = useAppSelector((state: RootState) => state.qrCodeURL); + console.log("qrcode string:", qrcode); - const [attendee, setAttendee] = useState(null); - const [qrcode, setQRCode] = useState(null); - const [token, _] = useState(useAppSelector((state: RootState) => state.token)); - useLayoutEffect(() => { - console.log("in use effect", token); - axios.get('https://api.reflectionsprojections.org/attendee/', { - headers: { - Authorization: token - } - }).then(attendeeData => { - console.log(attendeeData.data); - if (token) { - setAttendee(attendeeData.data); - } - }); - }, []); + useEffect(() => { + if (token && !attendee) { + dispatch(getAttendee(token)); + } + if (token && !qrcode) { + dispatch(getQRCode(token)); + } + }, [token, attendee, qrcode, dispatch]); useEffect(() => { - console.log("in use effect", token); - const getQRCode = async () => { - const qrcode = await axios.get('https://api.reflectionsprojections.org/attendee/qr', { - headers: { - Authorization: token - } - }); - console.log(qrcode.data); + const fetchQRCode = async() => { if (token) { - setQRCode(qrcode.data); + await(dispatch(getQRCode(token))); } }; - - getQRCode(); + fetchQRCode(); const interval = setInterval(getQRCode, 20000); return () => clearInterval(interval); - }, [token]); + }, [token, dispatch]); return ( - + {attendee && qrcode && - - + + + + + - {attendee.name} - - - {`Food Wave: ${attendee.foodWave}`} - - + {attendee.name} + } ); diff --git a/yarn.lock b/yarn.lock index 4c8e7af..ff1a71d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3463,6 +3463,16 @@ __metadata: languageName: node linkType: hard +"@react-native-picker/picker@npm:^2.7.7": + version: 2.7.7 + resolution: "@react-native-picker/picker@npm:2.7.7" + peerDependencies: + react: "*" + react-native: "*" + checksum: 54e38c47ca07e70586789bbabe1a57c4e30f1cd071c0d7a7a113dc65ecb10e84bacae93c0445513351e2c41a6a86f880389bdd468f2572dbf1fdbb690a444c93 + languageName: node + linkType: hard + "@react-native/assets-registry@npm:0.74.84": version: 0.74.84 resolution: "@react-native/assets-registry@npm:0.74.84" @@ -5029,6 +5039,18 @@ __metadata: languageName: node linkType: hard +"ajv@npm:8.11.0": + version: 8.11.0 + resolution: "ajv@npm:8.11.0" + dependencies: + fast-deep-equal: ^3.1.1 + json-schema-traverse: ^1.0.0 + require-from-string: ^2.0.2 + uri-js: ^4.2.2 + checksum: 5e0ff226806763be73e93dd7805b634f6f5921e3e90ca04acdf8db81eed9d8d3f0d4c5f1213047f45ebbf8047ffe0c840fa1ef2ec42c3a644899f69aa72b5bef + languageName: node + linkType: hard + "ajv@npm:^6.12.4": version: 6.12.6 resolution: "ajv@npm:6.12.6" @@ -7005,6 +7027,57 @@ __metadata: languageName: node linkType: hard +"expo-dev-client@npm:^4.0.20": + version: 4.0.20 + resolution: "expo-dev-client@npm:4.0.20" + dependencies: + expo-dev-launcher: 4.0.22 + expo-dev-menu: 5.0.16 + expo-dev-menu-interface: 1.8.3 + expo-manifests: ~0.14.0 + expo-updates-interface: ~0.16.2 + peerDependencies: + expo: "*" + checksum: 251cd3b89680119ae8e249c24603dd4527f0299ae3854359e28339cc44e5a30d481321625e2e0acf0aec6c40d64b199c89ec59ae22ec77911fe5cc99f5629ed7 + languageName: node + linkType: hard + +"expo-dev-launcher@npm:4.0.22": + version: 4.0.22 + resolution: "expo-dev-launcher@npm:4.0.22" + dependencies: + ajv: 8.11.0 + expo-dev-menu: 5.0.16 + expo-manifests: ~0.14.0 + resolve-from: ^5.0.0 + semver: ^7.6.0 + peerDependencies: + expo: "*" + checksum: 17fcf24a4f2868c729cfe6ce893ed303ea16d42f64daf53d420245806d27288707c0ca55940c91fbccbaa5633a310ada14bc6a8da552af194450df311fc00227 + languageName: node + linkType: hard + +"expo-dev-menu-interface@npm:1.8.3": + version: 1.8.3 + resolution: "expo-dev-menu-interface@npm:1.8.3" + peerDependencies: + expo: "*" + checksum: 1c2aaa35d25013244131dac6111613f3279c70efbf4bff43736e1646755fe67961405b451160874e1f39709f2ea7fa9db233d4b4566150b206672bdd5fd95a56 + languageName: node + linkType: hard + +"expo-dev-menu@npm:5.0.16": + version: 5.0.16 + resolution: "expo-dev-menu@npm:5.0.16" + dependencies: + expo-dev-menu-interface: 1.8.3 + semver: ^7.5.4 + peerDependencies: + expo: "*" + checksum: 8cbcfc256b97e86acab42b9b45a755c36394610768499e3016ef81e39dc7ed1245b9da5debeaee218de12bf66443df96f468867bb0e735d3c92f35476fc0e7ac + languageName: node + linkType: hard + "expo-file-system@npm:~17.0.1": version: 17.0.1 resolution: "expo-file-system@npm:17.0.1" @@ -7025,6 +7098,13 @@ __metadata: languageName: node linkType: hard +"expo-json-utils@npm:~0.13.0": + version: 0.13.1 + resolution: "expo-json-utils@npm:0.13.1" + checksum: 6e8312c1d7070edd47e1b5f9c7473f0c48f24df26292f9030f002d7aa12b0ece685090f5ec8a7ac446efbf58be54396827b951f28f76b75dc0e1b1d1f9fb73d1 + languageName: node + linkType: hard + "expo-keep-awake@npm:~13.0.2": version: 13.0.2 resolution: "expo-keep-awake@npm:13.0.2" @@ -7044,6 +7124,18 @@ __metadata: languageName: node linkType: hard +"expo-manifests@npm:~0.14.0": + version: 0.14.3 + resolution: "expo-manifests@npm:0.14.3" + dependencies: + "@expo/config": ~9.0.0 + expo-json-utils: ~0.13.0 + peerDependencies: + expo: "*" + checksum: 20aa38cceddf0b02a31f5a6ef91b77584c78854184020f083337223713a4d13099bbed53200d8de7ba29d0010b3db51025e68a9329e35ec95f6a8ec58d0a9603 + languageName: node + linkType: hard + "expo-modules-autolinking@npm:1.11.1": version: 1.11.1 resolution: "expo-modules-autolinking@npm:1.11.1" @@ -7098,6 +7190,15 @@ __metadata: languageName: node linkType: hard +"expo-updates-interface@npm:~0.16.2": + version: 0.16.2 + resolution: "expo-updates-interface@npm:0.16.2" + peerDependencies: + expo: "*" + checksum: 8ffe17f576b3afbbd5cd20fd363f10adcbcdf0abdf0659f471f337440e36d975bd5b2953e8fbcfe5bacf4ba67cdc08906eff083000e9ae549ff7e05a2b7aba1d + languageName: node + linkType: hard + "expo-web-browser@npm:^13.0.3": version: 13.0.3 resolution: "expo-web-browser@npm:13.0.3" @@ -8804,6 +8905,13 @@ __metadata: languageName: node linkType: hard +"json-schema-traverse@npm:^1.0.0": + version: 1.0.0 + resolution: "json-schema-traverse@npm:1.0.0" + checksum: 02f2f466cdb0362558b2f1fd5e15cce82ef55d60cd7f8fa828cf35ba74330f8d767fcae5c5c2adb7851fa811766c694b9405810879bc4e1ddd78a7c0e03658ad + languageName: node + linkType: hard + "json-stable-stringify-without-jsonify@npm:^1.0.1": version: 1.0.1 resolution: "json-stable-stringify-without-jsonify@npm:1.0.1" @@ -8845,6 +8953,13 @@ __metadata: languageName: node linkType: hard +"jwt-decode@npm:^4.0.0": + version: 4.0.0 + resolution: "jwt-decode@npm:4.0.0" + checksum: 390e2edcb31a92e86c8cbdd1edeea4c0d62acd371f8a8f0a8878e499390c0ecf4c658b365c4e941e4ef37d0170e4ca650aaa49f99a45c0b9695a235b210154b0 + languageName: node + linkType: hard + "keyv@npm:^4.0.0, keyv@npm:^4.5.3": version: 4.5.4 resolution: "keyv@npm:4.5.4" @@ -9027,6 +9142,20 @@ __metadata: languageName: node linkType: hard +"lodash.isequal@npm:^4.5.0": + version: 4.5.0 + resolution: "lodash.isequal@npm:4.5.0" + checksum: da27515dc5230eb1140ba65ff8de3613649620e8656b19a6270afe4866b7bd461d9ba2ac8a48dcc57f7adac4ee80e1de9f965d89d4d81a0ad52bb3eec2609644 + languageName: node + linkType: hard + +"lodash.isobject@npm:^3.0.2": + version: 3.0.2 + resolution: "lodash.isobject@npm:3.0.2" + checksum: 6c1667cbc4494d0a13a3617a4b23278d6d02dac520311f2bbb43f16f2cf71d2e6eb9dec8057315b77459df4890c756a256a087d3f4baa44a79ab5d6c968b060e + languageName: node + linkType: hard + "lodash.merge@npm:^4.6.2": version: 4.6.2 resolution: "lodash.merge@npm:4.6.2" @@ -10753,6 +10882,9 @@ __metadata: languageName: node linkType: hard +"react-native-pager-view@npm:6.3.0": + version: 6.3.0 + resolution: "react-native-pager-view@npm:6.3.0" "react-native-pager-view@npm:6.3.0": version: 6.3.0 resolution: "react-native-pager-view@npm:6.3.0" @@ -10763,6 +10895,18 @@ __metadata: languageName: node linkType: hard +"react-native-picker-select@npm:^9.1.3": + version: 9.1.3 + resolution: "react-native-picker-select@npm:9.1.3" + dependencies: + lodash.isequal: ^4.5.0 + lodash.isobject: ^3.0.2 + peerDependencies: + "@react-native-picker/picker": ^2.4.0 + checksum: 2844785e60d51e59df37a73da7016423826ee39e02b0417cc2d61da46e2582709ce102b83537d416c70d2fe180bbbd8dff48a31e0453f94bce319bc84ea8515f + languageName: node + linkType: hard + "react-native-qrcode-svg@npm:^6.3.1": version: 6.3.1 resolution: "react-native-qrcode-svg@npm:6.3.1" @@ -11365,6 +11509,7 @@ __metadata: "@gluestack-ui/themed": ^1.1.30 "@react-native-async-storage/async-storage": ^1.23.1 "@react-native-community/viewpager": ^5.0.11 + "@react-native-picker/picker": ^2.7.7 "@react-navigation/bottom-tabs": ^6.5.20 "@react-navigation/material-top-tabs": ^6.6.13 "@react-navigation/native": ^6.1.17 @@ -11383,17 +11528,20 @@ __metadata: expo: latest expo-app-loading: ^2.1.1 expo-camera: ^15.0.11 + expo-dev-client: ^4.0.20 expo-linking: ^6.3.1 expo-splash-screen: ~0.27.5 expo-status-bar: ~1.12.1 expo-web-browser: ^13.0.3 install: ^0.13.0 + jwt-decode: ^4.0.0 lucide-react-native: ^0.381.0 react: 18.2.0 react-native: 0.74.2 react-native-gesture-handler: ^2.16.0 react-native-modal: ^13.0.1 react-native-pager-view: 6.3.0 + react-native-picker-select: ^9.1.3 react-native-qrcode-svg: ^6.3.1 react-native-reanimated: ~3.10.1 react-native-safe-area-context: 4.10.1