From 393379df03002f35ec2d427eeb38c8fb94b1b13d Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Mon, 9 Sep 2024 05:41:54 +0530 Subject: [PATCH 1/9] Added authentication to the app to enable loging and signup --- components/Authentication/AuthScreen.js | 221 ++++++++++++++++++++++++ firebase/firebaseConfig.js | 5 +- package-lock.json | 57 +++++- package.json | 4 +- screens/HomeScreen/HomeScreen.js | 37 ++-- 5 files changed, 307 insertions(+), 17 deletions(-) create mode 100644 components/Authentication/AuthScreen.js diff --git a/components/Authentication/AuthScreen.js b/components/Authentication/AuthScreen.js new file mode 100644 index 0000000..f3aa257 --- /dev/null +++ b/components/Authentication/AuthScreen.js @@ -0,0 +1,221 @@ +import React, { useState } from "react"; +import { + View, + TextInput, + TouchableOpacity, + StyleSheet, + Text, + Image, + SafeAreaView, +} from "react-native"; +import { + createUserWithEmailAndPassword, + signInWithEmailAndPassword, +} from "firebase/auth"; +import { auth } from "@/firebase/firebaseConfig"; + +const AuthScreen = () => { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [signInFailedError, setSignInFailedError] = useState(); + + const handleSignUp = async () => { + try { + await createUserWithEmailAndPassword(auth, email, password); + } catch (error) { + console.error(error); + if (error.code === "auth/user-disabled") { + // Handle the specific case of a disabled user + setSignInFailedError( + "This user account has been disabled. Please contact support." + ); + } else if (error.code === "auth/invalid-credential") { + setSignInFailedError("Invalid credentials provided. Please try again."); + } else { + // Handle other types of errors + console.error("Sign-in error:", error.message); + // You can set a generic error message for other types of errors + setSignInFailedError( + "An error occurred during sign-up. Please try again." + ); + } + } + }; + + const handleSignIn = async () => { + try { + await signInWithEmailAndPassword(auth, email, password); + } catch (error) { + console.error(error); + if (error.code === "auth/user-disabled") { + setSignInFailedError( + "This user account has been disabled. Please contact support." + ); + } else if (error.code === "auth/invalid-credential") { + setSignInFailedError("Invalid credentials provided. Please try again."); + } else { + // Handle other types of errors + console.error("Sign-in error:", error.message); + // You can set a generic error message for other types of errors + setSignInFailedError( + "An error occurred during sign-in. Please try again." + ); + } + } + }; + + return ( + + + + + Welcome to Waiter + + Your one-stop solution for restaurant management + + + + ✉️ + + + + + 🔒 + + + + + Sign In + + + + Sign Up + + + {signInFailedError ? ( + {signInFailedError} + ) : ( + <> + )} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#f5f5f5", + }, + background: { + position: "absolute", + top: 0, + left: 0, + right: 0, + bottom: 0, + backgroundColor: "#f5f5f5", + }, + contentContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + padding: 20, + maxWidth: 500, + width: "100%", + alignSelf: "center", + backgroundColor: "white", + borderRadius: 10, + shadowColor: "#000", + shadowOffset: { + width: 0, + height: 2, + }, + shadowOpacity: 0.25, + shadowRadius: 3.84, + elevation: 5, + }, + logo: { + width: 100, + height: 100, + marginBottom: 20, + }, + welcomeText: { + fontSize: 24, + fontWeight: "bold", + marginBottom: 10, + color: "#333", + }, + subText: { + fontSize: 16, + color: "#666", + marginBottom: 30, + textAlign: "center", + }, + inputContainer: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#f5f5f5", + borderRadius: 5, + marginBottom: 15, + paddingHorizontal: 10, + width: "100%", + }, + inputIcon: { + fontSize: 20, + marginRight: 10, + }, + input: { + flex: 1, + height: 50, + color: "#333", + }, + button: { + backgroundColor: "#007AFF", + paddingVertical: 15, + paddingHorizontal: 30, + borderRadius: 5, + marginBottom: 10, + width: "100%", + alignItems: "center", + }, + buttonText: { + color: "#fff", + fontSize: 18, + fontWeight: "bold", + }, + signUpButton: { + backgroundColor: "#fff", + borderWidth: 1, + borderColor: "#007AFF", + }, + signUpText: { + color: "#007AFF", + }, + termsText: { + fontSize: 12, + color: "red", + textAlign: "center", + marginTop: 20, + }, +}); + +export default AuthScreen; diff --git a/firebase/firebaseConfig.js b/firebase/firebaseConfig.js index 1a2c482..8ab0557 100644 --- a/firebase/firebaseConfig.js +++ b/firebase/firebaseConfig.js @@ -1,5 +1,6 @@ import { initializeApp } from "firebase/app"; import { initializeFirestore } from "firebase/firestore"; +import { getAuth } from "firebase/auth"; const firebaseConfig = { apiKey: "AIzaSyAN-nxJtF6ROGWMjLboI4dEBKDNGnsMIWg", @@ -13,10 +14,12 @@ const firebaseConfig = { const app = initializeApp(firebaseConfig); +const auth = getAuth(app); + // Initialize Firestore with persistent local cache const db = initializeFirestore(app, { experimentalForceLongPolling: true, // Optional: Use if you face network issues synchronizeTabs: true, }); -export { db }; +export { auth, db }; diff --git a/package-lock.json b/package-lock.json index f1b669d..33a9652 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,8 @@ "@expo/metro-config": "^0.18.9", "@expo/vector-icons": "^14.0.2", "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", - "@react-native-firebase/app": "^20.1.0", + "@react-native-firebase/app": "^20.4.0", + "@react-native-firebase/auth": "^20.4.0", "@react-native-firebase/firestore": "^20.1.0", "@react-native-picker/picker": "^2.7.7", "@react-navigation/drawer": "^6.7.2", @@ -30,6 +31,7 @@ "expo-system-ui": "~3.0.7", "expo-web-browser": "~13.0.3", "firebase": "^10.12.3", + "firebaseui": "^6.1.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "lottie-react-native": "^6.7.2", @@ -7324,9 +7326,10 @@ } }, "node_modules/@react-native-firebase/app": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-20.3.0.tgz", - "integrity": "sha512-vmlu3g59wk0C8SmunLOg+6fC99kUe+ZlfBc7h9zdcFbnK+/6XScqKkG8B0yP3ANjrk9ZqWL1+M705m/uoo/WCQ==", + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/app/-/app-20.4.0.tgz", + "integrity": "sha512-I3YswH5tq0kSezyFwyV0d3J+lnH+N/SdznFZ70Lr671X0YlrbEBb6BK7FzEiwq8rKeIPh+pbgnbYwzj/k9uEnQ==", + "license": "Apache-2.0", "dependencies": { "firebase": "10.12.2", "superstruct": "^0.6.2" @@ -7803,6 +7806,24 @@ "node": ">=14.0" } }, + "node_modules/@react-native-firebase/auth": { + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/auth/-/auth-20.4.0.tgz", + "integrity": "sha512-LUmlS9m62QdDoqJKxPyZIBLne1O7ZznY3QjGh5tfU1QVk4DM/b1iG4wR06nyEK9PqzFY5BdqWwWAKbquXl78HQ==", + "license": "Apache-2.0", + "dependencies": { + "plist": "^3.1.0" + }, + "peerDependencies": { + "@react-native-firebase/app": "20.4.0", + "expo": ">=47.0.0" + }, + "peerDependenciesMeta": { + "expo": { + "optional": true + } + } + }, "node_modules/@react-native-firebase/firestore": { "version": "20.3.0", "resolved": "https://registry.npmjs.org/@react-native-firebase/firestore/-/firestore-20.3.0.tgz", @@ -11099,6 +11120,12 @@ "node": ">=8" } }, + "node_modules/dialog-polyfill": { + "version": "0.4.10", + "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.4.10.tgz", + "integrity": "sha512-j5yGMkP8T00UFgyO+78OxiN5vC5dzRQF3BEio+LhNvDbyfxWBsi3sfPArDm54VloaJwy2hm3erEiDWqHRC8rzw==", + "license": "BSD" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -12989,6 +13016,19 @@ "@firebase/vertexai-preview": "0.0.3" } }, + "node_modules/firebaseui": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/firebaseui/-/firebaseui-6.1.0.tgz", + "integrity": "sha512-5WiVYVxPGMANuZKxg6KLyU1tyqIsbqf/59Zm4HrdFYwPtM5lxxB0THvgaIk4ix+hCgF0qmY89sKiktcifKzGIA==", + "license": "Apache-2.0", + "dependencies": { + "dialog-polyfill": "^0.4.7", + "material-design-lite": "^1.2.0" + }, + "peerDependencies": { + "firebase": "^9.1.3 || ^10.0.0" + } + }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -17305,6 +17345,15 @@ "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==" }, + "node_modules/material-design-lite": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/material-design-lite/-/material-design-lite-1.3.0.tgz", + "integrity": "sha512-ao76b0bqSTKcEMt7Pui+J/S3eVF0b3GWfuKUwfe2lP5DKlLZOwBq37e0/bXEzxrw7/SuHAuYAdoCwY6mAYhrsg==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", diff --git a/package.json b/package.json index ea40257..b18be02 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "@expo/metro-config": "^0.18.9", "@expo/vector-icons": "^14.0.2", "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", - "@react-native-firebase/app": "^20.1.0", + "@react-native-firebase/app": "^20.4.0", + "@react-native-firebase/auth": "^20.4.0", "@react-native-firebase/firestore": "^20.1.0", "@react-native-picker/picker": "^2.7.7", "@react-navigation/drawer": "^6.7.2", @@ -37,6 +38,7 @@ "expo-system-ui": "~3.0.7", "expo-web-browser": "~13.0.3", "firebase": "^10.12.3", + "firebaseui": "^6.1.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "lottie-react-native": "^6.7.2", diff --git a/screens/HomeScreen/HomeScreen.js b/screens/HomeScreen/HomeScreen.js index 9469515..df6c8bd 100644 --- a/screens/HomeScreen/HomeScreen.js +++ b/screens/HomeScreen/HomeScreen.js @@ -13,6 +13,9 @@ import NavigationMenu from "@/components/NavigationMenu/NavigationMenu"; import { useNavigation } from "@react-navigation/native"; import { fetchHotelData } from "@/firebase/queries"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; +import AuthScreen from "@/components/Authentication/AuthScreen"; +import { auth } from "@/firebase/firebaseConfig"; +import { onAuthStateChanged } from "firebase/auth"; export default function HomeScreen() { const navigation = useNavigation(); @@ -23,12 +26,20 @@ export default function HomeScreen() { const [notifications, setNotifications] = useState([]); const [overviewItems, setOverviewItems] = useState([]); + const [user, setUser] = useState(null); + + useEffect(() => { + const subscriber = onAuthStateChanged(auth, (user) => { + setUser(user); + // if (initializing) setInitializing(false); + }); + return subscriber; + }, []); + useEffect(() => { const fetchHotelDetails = async () => { const hotelDetailsArray = await fetchHotelData(); if (hotelDetailsArray) { - console.log("Hotel details fetched", hotelDetailsArray); - // Convert array to an object const hotelDetails = hotelDetailsArray.reduce((acc, item) => { acc[item.id] = item; @@ -94,17 +105,21 @@ export default function HomeScreen() { ); if (isLoading) { - return ; + ; } - return ( - index.toString()} - renderItem={null} - /> - ); + if (!user) { + return ; + } else { + return ( + index.toString()} + renderItem={null} + /> + ); + } } const styles = StyleSheet.create({ From 75176fdb7dfcc06d2889354ef6e912fbf7cb9746 Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Mon, 9 Sep 2024 05:50:03 +0530 Subject: [PATCH 2/9] Fixed the bug of the menu categories screen not being scrollable. --- screens/MenuScreen/CategoryManagementPopup.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/screens/MenuScreen/CategoryManagementPopup.js b/screens/MenuScreen/CategoryManagementPopup.js index a16d131..04c777c 100644 --- a/screens/MenuScreen/CategoryManagementPopup.js +++ b/screens/MenuScreen/CategoryManagementPopup.js @@ -74,6 +74,7 @@ const CategoryManagementPopup = ({ ( @@ -103,7 +104,7 @@ const CategoryManagementPopup = ({ }; const styles = StyleSheet.create({ - container: { padding: 20 }, + container: { flex: 1, padding: 20 }, title: { fontSize: 20, fontWeight: "bold" }, input: { borderColor: "gray", borderWidth: 1, marginBottom: 10, padding: 8 }, buttons: { flexDirection: "row", justifyContent: "space-between" }, From ccfcf04897d189413535f9efbe9c7918fa1a53d9 Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Tue, 10 Sep 2024 02:30:28 +0530 Subject: [PATCH 3/9] Provided sign in and sign up options for the staffs --- components/Authentication/AuthScreen.js | 187 +++++++++++++++++++----- package-lock.json | 40 +---- package.json | 3 +- 3 files changed, 155 insertions(+), 75 deletions(-) diff --git a/components/Authentication/AuthScreen.js b/components/Authentication/AuthScreen.js index f3aa257..8834e37 100644 --- a/components/Authentication/AuthScreen.js +++ b/components/Authentication/AuthScreen.js @@ -13,28 +13,51 @@ import { signInWithEmailAndPassword, } from "firebase/auth"; import { auth } from "@/firebase/firebaseConfig"; +import { setDoc, doc } from "firebase/firestore"; +import { db } from "@/firebase/firebaseConfig"; +import { Picker } from "@react-native-picker/picker"; const AuthScreen = () => { + const [name, setName] = useState(""); + const [age, setAge] = useState(""); + const [role, setRole] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [signInFailedError, setSignInFailedError] = useState(); + const [isSignUpMode, setIsSignUpMode] = useState(false); + + const toggleMode = () => { + setIsSignUpMode((prevMode) => !prevMode); + }; const handleSignUp = async () => { try { - await createUserWithEmailAndPassword(auth, email, password); + // Create the user with email and password + const userCredential = await createUserWithEmailAndPassword( + auth, + email, + password + ); + const user = userCredential.user; + + // Add additional user details to Firestore + await setDoc(doc(db, "users", user.uid), { + name: name, + age: age, + email: email, + role: role, + createdAt: Date.now(), + }); + + console.log("User signed up successfully with ID:", user.uid); + // You can navigate to the next screen or update UI here } catch (error) { console.error(error); - if (error.code === "auth/user-disabled") { - // Handle the specific case of a disabled user + if (error.code === "auth/email-already-in-use") { setSignInFailedError( - "This user account has been disabled. Please contact support." + "This email is already in use. Please try another one." ); - } else if (error.code === "auth/invalid-credential") { - setSignInFailedError("Invalid credentials provided. Please try again."); } else { - // Handle other types of errors - console.error("Sign-in error:", error.message); - // You can set a generic error message for other types of errors setSignInFailedError( "An error occurred during sign-up. Please try again." ); @@ -44,7 +67,24 @@ const AuthScreen = () => { const handleSignIn = async () => { try { - await signInWithEmailAndPassword(auth, email, password); + const userCredential = await signInWithEmailAndPassword( + auth, + email, + password + ); + // const user = userCredential.user; + + // Retrieve additional user details from Firestore + // const userDoc = await getDoc(doc(db, "users", user.uid)); + // if (userDoc.exists()) { + // const userData = userDoc.data(); + // console.log("User signed in:", user.uid, userData); + // // You can store user data in state or context here + // } else { + // console.log("No additional user data found"); + // } + + // Navigate to the next screen or update UI } catch (error) { console.error(error); if (error.code === "auth/user-disabled") { @@ -54,9 +94,6 @@ const AuthScreen = () => { } else if (error.code === "auth/invalid-credential") { setSignInFailedError("Invalid credentials provided. Please try again."); } else { - // Handle other types of errors - console.error("Sign-in error:", error.message); - // You can set a generic error message for other types of errors setSignInFailedError( "An error occurred during sign-in. Please try again." ); @@ -69,7 +106,7 @@ const AuthScreen = () => { Welcome to Waiter @@ -89,6 +126,48 @@ const AuthScreen = () => { /> + {isSignUpMode && ( + <> + + 👤 + + + + + + 🎂 + + + + 👤 + setRole(itemValue)} + > + + + + + + + + + )} + 🔒 { value={password} onChangeText={setPassword} secureTextEntry + keyboardType="visible-password" /> - - Sign In - - - Sign Up + + {isSignUpMode ? "Sign Up" : "Sign In"} + + + + + + {isSignUpMode + ? "Already have an account! Sign In >>>" + : "Don't have an account? Sign Up >>>"} + {signInFailedError ? ( {signInFailedError} - ) : ( - <> - )} + ) : null} ); @@ -144,13 +228,14 @@ const styles = StyleSheet.create({ alignSelf: "center", backgroundColor: "white", borderRadius: 10, - shadowColor: "#000", - shadowOffset: { - width: 0, - height: 2, - }, - shadowOpacity: 0.25, - shadowRadius: 3.84, + // Provides a shadow to seperate the background from the main view + // shadowColor: "#000", + // shadowOffset: { + // width: 0, + // height: 2, + // }, + // shadowOpacity: 0.25, + // shadowRadius: 3.84, elevation: 5, }, logo: { @@ -179,6 +264,32 @@ const styles = StyleSheet.create({ paddingHorizontal: 10, width: "100%", }, + rowContainer: { + flexDirection: "row", + justifyContent: "space-between", + width: "100%", + marginBottom: 15, + }, + splitInputContainer: { + flexDirection: "row", + alignItems: "center", + backgroundColor: "#f5f5f5", + borderRadius: 5, + paddingHorizontal: 10, + width: "48%", + height: 50, + borderWidth: 1, + borderColor: "#ccc", + }, + picker: { + flex: 1, + height: 49, + color: "#333", + backgroundColor: "#f5f5f5", + borderRadius: 5, + borderWidth: 0, + paddingHorizontal: 5, + }, inputIcon: { fontSize: 20, marginRight: 10, @@ -202,20 +313,18 @@ const styles = StyleSheet.create({ fontSize: 18, fontWeight: "bold", }, - signUpButton: { - backgroundColor: "#fff", - borderWidth: 1, - borderColor: "#007AFF", - }, - signUpText: { - color: "#007AFF", - }, termsText: { fontSize: 12, color: "red", textAlign: "center", marginTop: 20, }, + toggleText: { + color: "#007AFF", + fontSize: 12, + marginTop: 10, + textAlign: "center", + }, }); export default AuthScreen; diff --git a/package-lock.json b/package-lock.json index 33a9652..3c51a1d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", "@react-native-firebase/app": "^20.4.0", "@react-native-firebase/auth": "^20.4.0", - "@react-native-firebase/firestore": "^20.1.0", + "@react-native-firebase/firestore": "^20.4.0", "@react-native-picker/picker": "^2.7.7", "@react-navigation/drawer": "^6.7.2", "@react-navigation/native": "^6.1.18", @@ -31,7 +31,6 @@ "expo-system-ui": "~3.0.7", "expo-web-browser": "~13.0.3", "firebase": "^10.12.3", - "firebaseui": "^6.1.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "lottie-react-native": "^6.7.2", @@ -7825,11 +7824,12 @@ } }, "node_modules/@react-native-firebase/firestore": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/@react-native-firebase/firestore/-/firestore-20.3.0.tgz", - "integrity": "sha512-bCFjM5FBshsyRD8UQfPlzbHoOshNZ+oVSFiWP6pz1jp9mumTfTaEi0pEqqRhGuxX2mDA/1lJQT44RXfHTAQ3yQ==", + "version": "20.4.0", + "resolved": "https://registry.npmjs.org/@react-native-firebase/firestore/-/firestore-20.4.0.tgz", + "integrity": "sha512-///YWJmZxCbDKM9QxaWnnEqvclIUbjsX+57hiwnzXzlNOd8dzOP1ub7EgBrzgZOT1VfybG9Fz16xTMjhxv21iQ==", + "license": "Apache-2.0", "peerDependencies": { - "@react-native-firebase/app": "20.3.0" + "@react-native-firebase/app": "20.4.0" } }, "node_modules/@react-native-picker/picker": { @@ -11120,12 +11120,6 @@ "node": ">=8" } }, - "node_modules/dialog-polyfill": { - "version": "0.4.10", - "resolved": "https://registry.npmjs.org/dialog-polyfill/-/dialog-polyfill-0.4.10.tgz", - "integrity": "sha512-j5yGMkP8T00UFgyO+78OxiN5vC5dzRQF3BEio+LhNvDbyfxWBsi3sfPArDm54VloaJwy2hm3erEiDWqHRC8rzw==", - "license": "BSD" - }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -13016,19 +13010,6 @@ "@firebase/vertexai-preview": "0.0.3" } }, - "node_modules/firebaseui": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/firebaseui/-/firebaseui-6.1.0.tgz", - "integrity": "sha512-5WiVYVxPGMANuZKxg6KLyU1tyqIsbqf/59Zm4HrdFYwPtM5lxxB0THvgaIk4ix+hCgF0qmY89sKiktcifKzGIA==", - "license": "Apache-2.0", - "dependencies": { - "dialog-polyfill": "^0.4.7", - "material-design-lite": "^1.2.0" - }, - "peerDependencies": { - "firebase": "^9.1.3 || ^10.0.0" - } - }, "node_modules/flat-cache": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", @@ -17345,15 +17326,6 @@ "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==" }, - "node_modules/material-design-lite": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/material-design-lite/-/material-design-lite-1.3.0.tgz", - "integrity": "sha512-ao76b0bqSTKcEMt7Pui+J/S3eVF0b3GWfuKUwfe2lP5DKlLZOwBq37e0/bXEzxrw7/SuHAuYAdoCwY6mAYhrsg==", - "license": "Apache-2.0", - "engines": { - "node": ">=0.12.0" - } - }, "node_modules/md5": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", diff --git a/package.json b/package.json index b18be02..f47148c 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", "@react-native-firebase/app": "^20.4.0", "@react-native-firebase/auth": "^20.4.0", - "@react-native-firebase/firestore": "^20.1.0", + "@react-native-firebase/firestore": "^20.4.0", "@react-native-picker/picker": "^2.7.7", "@react-navigation/drawer": "^6.7.2", "@react-navigation/native": "^6.1.18", @@ -38,7 +38,6 @@ "expo-system-ui": "~3.0.7", "expo-web-browser": "~13.0.3", "firebase": "^10.12.3", - "firebaseui": "^6.1.0", "html2canvas": "^1.4.1", "jspdf": "^2.5.1", "lottie-react-native": "^6.7.2", From 354cd21fd6036a85c15a9b46575f1684061b2fe1 Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Tue, 10 Sep 2024 03:11:03 +0530 Subject: [PATCH 4/9] Improved the authentication to be available across the screens --- app/(tabs)/_layout.js | 159 +++++++++--------- components/Authentication/AuthProvider.js | 22 +++ screens/HomeScreen/HomeScreen.js | 38 ++--- .../InventoryScreenContainer.js | 7 +- screens/OrdersScreen/OrdersScreenContainer.js | 7 +- screens/StaffsScreen/StaffsScreenContainer.js | 7 +- .../TablesScreen/RestaurantTablesScreen.js | 7 +- 7 files changed, 140 insertions(+), 107 deletions(-) create mode 100644 components/Authentication/AuthProvider.js diff --git a/app/(tabs)/_layout.js b/app/(tabs)/_layout.js index fad66f4..ba2d29a 100644 --- a/app/(tabs)/_layout.js +++ b/app/(tabs)/_layout.js @@ -3,89 +3,92 @@ import React from "react"; import { TabBarIcon } from "@/components/navigation/TabBarIcon"; import { Colors } from "@/constants/Colors"; import { useColorScheme } from "@/hooks/useColorScheme"; +import { AuthProvider } from "@/components/Authentication/AuthProvider"; export default function TabLayout() { const colorScheme = useColorScheme(); return ( - - ( - - ), + + - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - ( - - ), - }} - /> - + > + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); } diff --git a/components/Authentication/AuthProvider.js b/components/Authentication/AuthProvider.js new file mode 100644 index 0000000..900bf99 --- /dev/null +++ b/components/Authentication/AuthProvider.js @@ -0,0 +1,22 @@ +import React, { createContext, useState, useEffect } from "react"; +import { onAuthStateChanged } from "firebase/auth"; +import { auth } from "@/firebase/firebaseConfig"; + +const AuthContext = createContext(); + +export const AuthProvider = ({ children }) => { + const [user, setUser] = useState(null); + + useEffect(() => { + const subscriber = onAuthStateChanged(auth, (user) => { + setUser(user); + }); + return subscriber; + }, []); + + return ( + {children} + ); +}; + +export default AuthContext; diff --git a/screens/HomeScreen/HomeScreen.js b/screens/HomeScreen/HomeScreen.js index df6c8bd..4209a88 100644 --- a/screens/HomeScreen/HomeScreen.js +++ b/screens/HomeScreen/HomeScreen.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from "react"; +import React, { useEffect, useState, useContext } from "react"; import { FlatList, StyleSheet, View } from "react-native"; import { widthPercentageToDP as wp, @@ -14,8 +14,7 @@ import { useNavigation } from "@react-navigation/native"; import { fetchHotelData } from "@/firebase/queries"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; import AuthScreen from "@/components/Authentication/AuthScreen"; -import { auth } from "@/firebase/firebaseConfig"; -import { onAuthStateChanged } from "firebase/auth"; +import AuthContext from "@/components/Authentication/AuthProvider"; export default function HomeScreen() { const navigation = useNavigation(); @@ -26,16 +25,6 @@ export default function HomeScreen() { const [notifications, setNotifications] = useState([]); const [overviewItems, setOverviewItems] = useState([]); - const [user, setUser] = useState(null); - - useEffect(() => { - const subscriber = onAuthStateChanged(auth, (user) => { - setUser(user); - // if (initializing) setInitializing(false); - }); - return subscriber; - }, []); - useEffect(() => { const fetchHotelDetails = async () => { const hotelDetailsArray = await fetchHotelData(); @@ -104,22 +93,21 @@ export default function HomeScreen() { ); + // const { user } = useContext(AuthContext); + // if (!user) return ; + if (isLoading) { ; } - if (!user) { - return ; - } else { - return ( - index.toString()} - renderItem={null} - /> - ); - } + return ( + index.toString()} + renderItem={null} + /> + ); } const styles = StyleSheet.create({ diff --git a/screens/InventoryScreen/InventoryScreenContainer.js b/screens/InventoryScreen/InventoryScreenContainer.js index 5ba3a74..9cd9082 100644 --- a/screens/InventoryScreen/InventoryScreenContainer.js +++ b/screens/InventoryScreen/InventoryScreenContainer.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { collection, getDocs, @@ -11,6 +11,8 @@ import { db } from "@/firebase/firebaseConfig"; import InventoryScreenView from "./InventoryScreenView"; import { generateUniqueKey } from "@/utils/keyGenerator"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import AuthScreen from "@/components/Authentication/AuthScreen"; const InventoryScreenContainer = () => { const [isLoading, setIsLoading] = useState(false); @@ -89,6 +91,9 @@ const InventoryScreenContainer = () => { } }; + const { user } = useContext(AuthContext); + if (!user) return ; + if (isLoading) { return ; } diff --git a/screens/OrdersScreen/OrdersScreenContainer.js b/screens/OrdersScreen/OrdersScreenContainer.js index 5b2a5cb..806f037 100644 --- a/screens/OrdersScreen/OrdersScreenContainer.js +++ b/screens/OrdersScreen/OrdersScreenContainer.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { doc, getDoc, @@ -16,6 +16,8 @@ import { completedOrdersCount, activeOrdersCount, } from "@/utils/orderManagement"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import AuthScreen from "@/components/Authentication/AuthScreen"; const OrdersScreenContainer = () => { const [orders, setOrders] = useState([]); @@ -131,6 +133,9 @@ const OrdersScreenContainer = () => { } }; + const { user } = useContext(AuthContext); + if (!user) return ; + if (loading) { return ; } diff --git a/screens/StaffsScreen/StaffsScreenContainer.js b/screens/StaffsScreen/StaffsScreenContainer.js index 687a592..89d8df2 100644 --- a/screens/StaffsScreen/StaffsScreenContainer.js +++ b/screens/StaffsScreen/StaffsScreenContainer.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { collection, getDocs, @@ -11,6 +11,8 @@ import { db } from "@/firebase/firebaseConfig"; import StaffScreenView from "./StaffScreenView"; import { generateUniqueKey } from "@/utils/keyGenerator"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import AuthScreen from "@/components/Authentication/AuthScreen"; const StaffsScreenContainer = () => { const [isLoading, setIsLoading] = useState(false); @@ -87,6 +89,9 @@ const StaffsScreenContainer = () => { } }; + const { user } = useContext(AuthContext); + if (!user) return ; + if (isLoading) { return ; } diff --git a/screens/TablesScreen/RestaurantTablesScreen.js b/screens/TablesScreen/RestaurantTablesScreen.js index 85305de..ca4da58 100644 --- a/screens/TablesScreen/RestaurantTablesScreen.js +++ b/screens/TablesScreen/RestaurantTablesScreen.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { FlatList } from "react-native"; import TableList from "./TableList"; import { @@ -21,6 +21,8 @@ import { ThemedView } from "@/components/common/ThemedView"; import ThemedButton from "@/components/common/ThemedButton"; import { ThemedText } from "@/components/common/ThemedText"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import AuthScreen from "@/components/Authentication/AuthScreen"; const RestaurantTablesScreen = () => { const [isLoading, setIsLoading] = useState(false); @@ -246,6 +248,9 @@ const RestaurantTablesScreen = () => { setTableInfoOptionClicked(true); }; + const { user } = useContext(AuthContext); + if (!user) return ; + if (isLoading) { return ; } From aab0555ac57b70072f8f7ff033725e6890539c2c Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Wed, 11 Sep 2024 01:25:07 +0530 Subject: [PATCH 5/9] Added an admin screen to the layout. --- app/(tabs)/_layout.js | 13 ++ app/(tabs)/admin.js | 11 ++ components/Authentication/AuthScreen.js | 12 ++ package-lock.json | 20 ++ package.json | 4 +- screens/HomeScreen/HomeScreen.js | 2 + screens/ProfileScreen/ProfileScreen.js | 231 ++++++++++++++++++++++++ 7 files changed, 292 insertions(+), 1 deletion(-) create mode 100644 app/(tabs)/admin.js create mode 100644 screens/ProfileScreen/ProfileScreen.js diff --git a/app/(tabs)/_layout.js b/app/(tabs)/_layout.js index ba2d29a..8e0d683 100644 --- a/app/(tabs)/_layout.js +++ b/app/(tabs)/_layout.js @@ -88,6 +88,19 @@ export default function TabLayout() { ), }} /> + + ( + + ), + }} + /> ); diff --git a/app/(tabs)/admin.js b/app/(tabs)/admin.js new file mode 100644 index 0000000..2b839ad --- /dev/null +++ b/app/(tabs)/admin.js @@ -0,0 +1,11 @@ +import React from "react"; +import ProfileScreen from "@/screens/ProfileScreen/ProfileScreen"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; + +export default function MenuScreen() { + return ( + + + + ); +} diff --git a/components/Authentication/AuthScreen.js b/components/Authentication/AuthScreen.js index 8834e37..9acd82e 100644 --- a/components/Authentication/AuthScreen.js +++ b/components/Authentication/AuthScreen.js @@ -19,6 +19,7 @@ import { Picker } from "@react-native-picker/picker"; const AuthScreen = () => { const [name, setName] = useState(""); + const [mobile, setMobile] = useState(); const [age, setAge] = useState(""); const [role, setRole] = useState(""); const [email, setEmail] = useState(""); @@ -46,6 +47,7 @@ const AuthScreen = () => { age: age, email: email, role: role, + mobile: mobile, createdAt: Date.now(), }); @@ -128,6 +130,16 @@ const AuthScreen = () => { {isSignUpMode && ( <> + + 📞 + + 👤 navigation.navigate("tables") }, { title: "Menu", onPress: () => navigation.navigate("menu") }, { title: "Orders", onPress: () => navigation.navigate("orders") }, + { title: "Inventory", onPress: () => navigation.navigate("inventory") }, + { title: "Admin", onPress: () => navigation.navigate("admin") }, ]; const renderHeader = () => ( diff --git a/screens/ProfileScreen/ProfileScreen.js b/screens/ProfileScreen/ProfileScreen.js new file mode 100644 index 0000000..3df1183 --- /dev/null +++ b/screens/ProfileScreen/ProfileScreen.js @@ -0,0 +1,231 @@ +import React, { useState } from "react"; +import { + View, + Text, + Image, + TouchableOpacity, + StyleSheet, + ScrollView, + Dimensions, +} from "react-native"; +import { LinearGradient } from "expo-linear-gradient"; +import { BlurView } from "expo-blur"; +import Icon from "react-native-vector-icons/MaterialIcons"; +import { useNavigation } from "@react-navigation/native"; + +const ProfileScreen = () => { + const { width } = Dimensions.get("window"); + const isLargeScreen = width > 768; + + const navigation = useNavigation(); + + const [isEditing, setIsEditing] = useState(false); + const [userProfile, setUserProfile] = useState({ + name: "John Doe", + position: "Head Chef", + email: "john.doe@restaurant.com", + phone: "+1 234 567 8900", + }); + + const navigationOptions = [ + { title: "Approve Sign Up Requests", icon: "person-add" }, + { + title: "Checkout Menu", + icon: "restaurant-menu", + onPress: () => navigation.navigate("menu"), + }, + { + title: "Inventory", + icon: "inventory", + onPress: () => navigation.navigate("inventory"), + }, + { + title: "Employees", + icon: "people", + onPress: () => navigation.navigate("staffs"), + }, + { title: "Log Out", icon: "exit-to-app" }, + ]; + + const handleEdit = () => { + setIsEditing(!isEditing); + // Implement edit functionality here + }; + + return ( + + + + + + + + + {userProfile.name} + {userProfile.position} + + + {userProfile.email} + + + + {userProfile.phone} + + + + + + + + + + + {navigationOptions.map((option, index) => ( + + + + + {option.title} + + ))} + + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#f0f3f5", + }, + blurContainer: { + flex: 1, + padding: 20, + }, + profileContent: { + flex: 1, + justifyContent: "space-between", + position: "relative", + }, + profileImage: { + width: 120, + height: 120, + borderRadius: 60, + borderWidth: 3, + borderColor: "#fff", + marginBottom: 15, + }, + userDetails: { + flex: 1, + }, + name: { + fontSize: 28, + fontWeight: "bold", + color: "#fff", + marginBottom: 5, + }, + position: { + fontSize: 18, + color: "#e0e0e0", + marginBottom: 15, + }, + infoContainer: { + flexDirection: "row", + alignItems: "center", + marginBottom: 8, + }, + infoIcon: { + marginRight: 8, + }, + info: { + fontSize: 16, + color: "#fff", + }, + editButton: { + position: "absolute", + top: 10, + right: 10, + padding: 10, + backgroundColor: "rgba(255, 255, 255, 0.2)", + borderRadius: 20, + }, + navigationOptions: { + padding: 20, + }, + optionItem: { + flexDirection: "row", + alignItems: "center", + marginBottom: 15, + backgroundColor: "#fff", + borderRadius: 12, + padding: 15, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + elevation: 3, + }, + optionGradient: { + width: 50, + height: 50, + borderRadius: 25, + justifyContent: "center", + alignItems: "center", + marginRight: 15, + }, + optionText: { + fontSize: 18, + color: "#333", + fontWeight: "500", + }, +}); + +export default ProfileScreen; From b7265435840fe622d5c520373b3e6e772852a974 Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Thu, 12 Sep 2024 01:03:50 +0530 Subject: [PATCH 6/9] Added approver screen for sign up request approvals. --- .../ApproveSignUpRequestsScreen.js | 243 ++++++++++++++++++ components/Authentication/AuthScreen.js | 109 ++++---- screens/ProfileScreen/ProfileScreen.js | 10 +- 3 files changed, 298 insertions(+), 64 deletions(-) create mode 100644 components/Authentication/ApproveSignUpRequestsScreen.js diff --git a/components/Authentication/ApproveSignUpRequestsScreen.js b/components/Authentication/ApproveSignUpRequestsScreen.js new file mode 100644 index 0000000..3225dda --- /dev/null +++ b/components/Authentication/ApproveSignUpRequestsScreen.js @@ -0,0 +1,243 @@ +import React, { useEffect, useState } from "react"; +import { + View, + Text, + FlatList, + TouchableOpacity, + StyleSheet, + Alert, + ActivityIndicator, +} from "react-native"; +import { + collection, + query, + onSnapshot, + writeBatch, + doc, + deleteDoc, +} from "firebase/firestore"; +import { createUserWithEmailAndPassword } from "firebase/auth"; +import { db } from "@/firebase/firebaseConfig"; +import { auth } from "@/firebase/firebaseConfig"; +import { LinearGradient } from "expo-linear-gradient"; +import { Ionicons } from "@expo/vector-icons"; + +const ApproveSignUpRequestsScreen = () => { + const [requests, setRequests] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + const fetchAllSignupRequests = async () => { + try { + const signUpRequests = collection( + db, + "hotel-details/staff-details/signup-requests" + ); + const q = query(signUpRequests); + + // Set up real-time listener + const unsubscribe = onSnapshot(q, (querySnapshot) => { + const allRequests = []; + querySnapshot.docs.forEach((doc) => { + allRequests.push({ id: doc.id, ...doc.data() }); + }); + setRequests(allRequests); + setLoading(false); + }); + // Clean up the listener on component unmount + return () => unsubscribe(); + } catch (error) { + console.error("Error fetching user sign up requests:", error); + } + }; + + setLoading(true); + fetchAllSignupRequests(); + }, []); + + const handleSignUp = async ({ email, password }) => { + var errorMessage = ""; + try { + // Create the user with email and password + const userCredential = await createUserWithEmailAndPassword( + auth, + email, + password + ); + return userCredential.user.uid; + // You can navigate to the next screen or update UI here + } catch (error) { + console.error(error); + if (error.code === "auth/email-already-in-use") { + throw new Error( + "This email is already in use. Please try another one." + ); + } else { + throw new Error( + (errorMessage = "An error occurred during sign-up. Please try again.") + ); + } + } + }; + + const addToStaffs = async ({ name, email, role, age, mobile, authId }) => { + const batch = writeBatch(db); + const docRef = doc(collection(db, "hotel-details/staff-details/staffs")); + batch.set(docRef, { + name: name, + age: age, + email: email, + role: role, + authId: authId, + mobile: mobile, + createdAt: Date.now(), + }); + try { + await batch.commit(); + console.log("Batch write successful"); + } catch (error) { + console.error("Error writing batch:", error); + } + }; + + const deleteRequest = async ({ id }) => { + try { + const docRef = doc(db, "hotel-details/staff-details/signup-requests", id); + await deleteDoc(docRef); + console.log("Document successfully deleted!"); + } catch (error) { + console.error("Error removing document: ", error); + throw error; + } + }; + + const handleApprove = async (id) => { + console.log("id: ", id); + const request = requests.find((item) => item.id === id); + try { + setLoading(true); + request.authId = await handleSignUp(request); + console.log(request); + addToStaffs(request); + deleteRequest(request); + console.log("Approval requests updated"); + } catch (error) { + console.error(error); + Alert.alert("Failed to approve request", error.message); + } + setLoading(false); + }; + + const renderItem = ({ item }) => ( + + + {item.name} + {item.email} + + handleApprove(item.id)} + > + + + + + + ); + + if (loading) { + return ( + + + + ); + } + + if (loading) { + return ; + } + + return ( + + Approve Sign Up Requests + {requests.length === 0 ? ( + No pending requests + ) : ( + item.id} + contentContainerStyle={styles.listContainer} + /> + )} + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + backgroundColor: "#f5f5f5", + padding: 20, + }, + loadingContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + title: { + fontSize: 28, + fontWeight: "bold", + color: "#333", + marginBottom: 20, + textAlign: "center", + }, + listContainer: { + paddingBottom: 20, + }, + requestItem: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + backgroundColor: "#fff", + borderRadius: 10, + padding: 15, + marginBottom: 10, + elevation: 3, + shadowColor: "#000", + shadowOffset: { width: 0, height: 2 }, + shadowOpacity: 0.1, + shadowRadius: 4, + }, + requestInfo: { + flex: 1, + }, + requestName: { + fontSize: 18, + fontWeight: "600", + color: "#333", + }, + requestEmail: { + fontSize: 14, + color: "#666", + marginTop: 4, + }, + approveButton: { + marginLeft: 10, + }, + approveGradient: { + borderRadius: 20, + padding: 10, + }, + noRequestsText: { + fontSize: 18, + color: "#666", + textAlign: "center", + marginTop: 20, + }, +}); + +export default ApproveSignUpRequestsScreen; diff --git a/components/Authentication/AuthScreen.js b/components/Authentication/AuthScreen.js index 9acd82e..ee0e4f7 100644 --- a/components/Authentication/AuthScreen.js +++ b/components/Authentication/AuthScreen.js @@ -7,13 +7,11 @@ import { Text, Image, SafeAreaView, + Alert, } from "react-native"; -import { - createUserWithEmailAndPassword, - signInWithEmailAndPassword, -} from "firebase/auth"; +import { signInWithEmailAndPassword } from "firebase/auth"; import { auth } from "@/firebase/firebaseConfig"; -import { setDoc, doc } from "firebase/firestore"; +import { doc, writeBatch, collection } from "firebase/firestore"; import { db } from "@/firebase/firebaseConfig"; import { Picker } from "@react-native-picker/picker"; @@ -24,79 +22,60 @@ const AuthScreen = () => { const [role, setRole] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const [signInFailedError, setSignInFailedError] = useState(); + const [authReqResponse, setAuthReqResponse] = useState(); const [isSignUpMode, setIsSignUpMode] = useState(false); const toggleMode = () => { setIsSignUpMode((prevMode) => !prevMode); }; - const handleSignUp = async () => { + const handleSignUpRequest = async () => { + const batch = writeBatch(db); + const docRef = doc( + collection(db, "hotel-details/staff-details/signup-requests") + ); + batch.set(docRef, { + name: name, + age: age, + email: email, + role: role, + mobile: mobile, + password: password, + createdAt: Date.now(), + }); try { - // Create the user with email and password - const userCredential = await createUserWithEmailAndPassword( - auth, - email, - password + await batch.commit(); + console.log("Batch write successful"); + Alert.alert( + "Request submitted successfully", + "Your sign up request has been sent successfully. Please try to sign in when your manager approves the request." + ); + setAuthReqResponse( + "Try to sign in when your manager approves the request." ); - const user = userCredential.user; - - // Add additional user details to Firestore - await setDoc(doc(db, "users", user.uid), { - name: name, - age: age, - email: email, - role: role, - mobile: mobile, - createdAt: Date.now(), - }); - - console.log("User signed up successfully with ID:", user.uid); - // You can navigate to the next screen or update UI here } catch (error) { - console.error(error); - if (error.code === "auth/email-already-in-use") { - setSignInFailedError( - "This email is already in use. Please try another one." - ); - } else { - setSignInFailedError( - "An error occurred during sign-up. Please try again." - ); - } + console.error("Error writing batch:", error); + Alert.alert( + "Failed to submit request", + "An error occurred when submitting your sign up request. Please try again." + ); + setAuthReqResponse("Please try again."); } }; const handleSignIn = async () => { try { - const userCredential = await signInWithEmailAndPassword( - auth, - email, - password - ); - // const user = userCredential.user; - - // Retrieve additional user details from Firestore - // const userDoc = await getDoc(doc(db, "users", user.uid)); - // if (userDoc.exists()) { - // const userData = userDoc.data(); - // console.log("User signed in:", user.uid, userData); - // // You can store user data in state or context here - // } else { - // console.log("No additional user data found"); - // } - - // Navigate to the next screen or update UI + await signInWithEmailAndPassword(auth, email, password); } catch (error) { console.error(error); if (error.code === "auth/user-disabled") { - setSignInFailedError( + setAuthReqResponse( "This user account has been disabled. Please contact support." ); } else if (error.code === "auth/invalid-credential") { - setSignInFailedError("Invalid credentials provided. Please try again."); + setAuthReqResponse("Invalid credentials provided. Please try again."); } else { - setSignInFailedError( + setAuthReqResponse( "An error occurred during sign-in. Please try again." ); } @@ -171,9 +150,13 @@ const AuthScreen = () => { onValueChange={(itemValue) => setRole(itemValue)} > - - - + + + + + + + @@ -194,7 +177,7 @@ const AuthScreen = () => { {isSignUpMode ? "Sign Up" : "Sign In"} @@ -209,8 +192,8 @@ const AuthScreen = () => { - {signInFailedError ? ( - {signInFailedError} + {authReqResponse ? ( + {authReqResponse} ) : null} diff --git a/screens/ProfileScreen/ProfileScreen.js b/screens/ProfileScreen/ProfileScreen.js index 3df1183..6f8c4fd 100644 --- a/screens/ProfileScreen/ProfileScreen.js +++ b/screens/ProfileScreen/ProfileScreen.js @@ -12,6 +12,7 @@ import { LinearGradient } from "expo-linear-gradient"; import { BlurView } from "expo-blur"; import Icon from "react-native-vector-icons/MaterialIcons"; import { useNavigation } from "@react-navigation/native"; +import ApproveSignUpRequestsScreen from "@/components/Authentication/ApproveSignUpRequestsScreen"; const ProfileScreen = () => { const { width } = Dimensions.get("window"); @@ -26,9 +27,14 @@ const ProfileScreen = () => { email: "john.doe@restaurant.com", phone: "+1 234 567 8900", }); + const [openApproveRequests, setOpenApproveRequests] = useState(false); const navigationOptions = [ - { title: "Approve Sign Up Requests", icon: "person-add" }, + { + title: "Approve Sign Up Requests", + icon: "person-add", + onPress: () => setOpenApproveRequests(true), + }, { title: "Checkout Menu", icon: "restaurant-menu", @@ -52,6 +58,8 @@ const ProfileScreen = () => { // Implement edit functionality here }; + if (openApproveRequests) return ; + return ( Date: Thu, 12 Sep 2024 01:14:01 +0530 Subject: [PATCH 7/9] Submitted authentication request approval functionality is complete. UX improvements to be included. --- .../ApproveSignUpRequestsScreen.js | 22 ++++++++++++++----- firebase/firebaseConfig.js | 2 +- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/components/Authentication/ApproveSignUpRequestsScreen.js b/components/Authentication/ApproveSignUpRequestsScreen.js index 3225dda..fc09099 100644 --- a/components/Authentication/ApproveSignUpRequestsScreen.js +++ b/components/Authentication/ApproveSignUpRequestsScreen.js @@ -16,16 +16,24 @@ import { doc, deleteDoc, } from "firebase/firestore"; -import { createUserWithEmailAndPassword } from "firebase/auth"; +import { initializeApp } from "firebase/app"; +import { + getAuth, + createUserWithEmailAndPassword, + signOut, +} from "firebase/auth"; import { db } from "@/firebase/firebaseConfig"; -import { auth } from "@/firebase/firebaseConfig"; import { LinearGradient } from "expo-linear-gradient"; import { Ionicons } from "@expo/vector-icons"; +import { firebaseConfig } from "@/firebase/firebaseConfig"; const ApproveSignUpRequestsScreen = () => { const [requests, setRequests] = useState([]); const [loading, setLoading] = useState(false); + // Create a secondary app for user creation + const secondaryApp = initializeApp(firebaseConfig, "Secondary"); + useEffect(() => { const fetchAllSignupRequests = async () => { try { @@ -58,12 +66,16 @@ const ApproveSignUpRequestsScreen = () => { const handleSignUp = async ({ email, password }) => { var errorMessage = ""; try { + // Get auth from the secondary app + const secondaryAuth = getAuth(secondaryApp); // Create the user with email and password const userCredential = await createUserWithEmailAndPassword( - auth, + secondaryAuth, email, password ); + // Immediately sign out the user from the secondary app + await signOut(secondaryAuth); return userCredential.user.uid; // You can navigate to the next screen or update UI here } catch (error) { @@ -73,9 +85,7 @@ const ApproveSignUpRequestsScreen = () => { "This email is already in use. Please try another one." ); } else { - throw new Error( - (errorMessage = "An error occurred during sign-up. Please try again.") - ); + throw new Error("An error occurred during sign-up. Please try again."); } } }; diff --git a/firebase/firebaseConfig.js b/firebase/firebaseConfig.js index 8ab0557..e47cc3a 100644 --- a/firebase/firebaseConfig.js +++ b/firebase/firebaseConfig.js @@ -22,4 +22,4 @@ const db = initializeFirestore(app, { synchronizeTabs: true, }); -export { auth, db }; +export { auth, db, firebaseConfig }; From df2ec068ea7365f27a0297185cfb3b0415499746 Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Sat, 14 Sep 2024 14:11:08 +0530 Subject: [PATCH 8/9] Added entitlement driven views. Added sign up requests approval to enable secured logins --- .../ApproveSignUpRequestsScreen.js | 7 ++-- components/Authentication/AuthProvider.js | 36 +++++++++++++++++-- components/Authentication/AuthScreen.js | 7 ++-- .../Authentication/UnauthorizedScreen.js | 22 ++++++++++++ .../InventoryScreenContainer.js | 13 +++++++ screens/MenuScreen/MenuScreenContainer.js | 20 ++++++++++- screens/ProfileScreen/ProfileScreen.js | 20 ++++++++++- screens/StaffsScreen/StaffScreenView.js | 9 ++--- screens/StaffsScreen/StaffsScreenContainer.js | 13 +++++++ 9 files changed, 132 insertions(+), 15 deletions(-) create mode 100644 components/Authentication/UnauthorizedScreen.js diff --git a/components/Authentication/ApproveSignUpRequestsScreen.js b/components/Authentication/ApproveSignUpRequestsScreen.js index fc09099..495b061 100644 --- a/components/Authentication/ApproveSignUpRequestsScreen.js +++ b/components/Authentication/ApproveSignUpRequestsScreen.js @@ -64,7 +64,6 @@ const ApproveSignUpRequestsScreen = () => { }, []); const handleSignUp = async ({ email, password }) => { - var errorMessage = ""; try { // Get auth from the secondary app const secondaryAuth = getAuth(secondaryApp); @@ -131,11 +130,13 @@ const ApproveSignUpRequestsScreen = () => { addToStaffs(request); deleteRequest(request); console.log("Approval requests updated"); + Alert.alert("Success", "Request approved successfully"); } catch (error) { - console.error(error); + console.error("Error in handleApprove:", error); Alert.alert("Failed to approve request", error.message); + } finally { + setLoading(false); } - setLoading(false); }; const renderItem = ({ item }) => ( diff --git a/components/Authentication/AuthProvider.js b/components/Authentication/AuthProvider.js index 900bf99..15426e3 100644 --- a/components/Authentication/AuthProvider.js +++ b/components/Authentication/AuthProvider.js @@ -1,6 +1,8 @@ import React, { createContext, useState, useEffect } from "react"; import { onAuthStateChanged } from "firebase/auth"; import { auth } from "@/firebase/firebaseConfig"; +import { collection, getDocs, query, where } from "firebase/firestore"; +import { db } from "@/firebase/firebaseConfig"; const AuthContext = createContext(); @@ -8,10 +10,38 @@ export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); useEffect(() => { - const subscriber = onAuthStateChanged(auth, (user) => { - setUser(user); + const subscriber = onAuthStateChanged(auth, async (user) => { + if (user) { + try { + const staffsRef = collection( + db, + "hotel-details/staff-details/staffs" + ); + const q = query(staffsRef, where("authId", "==", user.uid)); + const querySnapshot = await getDocs(q); + + const staffs = querySnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); + if (staffs.length > 0) { + const updatedUser = { + ...user, + staffDetails: staffs[0], + }; + setUser(updatedUser); + } else { + setUser(user); + } + } catch (error) { + setUser(user); + } + } else { + setUser(user); + } }); - return subscriber; + + return () => subscriber(); }, []); return ( diff --git a/components/Authentication/AuthScreen.js b/components/Authentication/AuthScreen.js index ee0e4f7..3f79d47 100644 --- a/components/Authentication/AuthScreen.js +++ b/components/Authentication/AuthScreen.js @@ -17,12 +17,12 @@ import { Picker } from "@react-native-picker/picker"; const AuthScreen = () => { const [name, setName] = useState(""); - const [mobile, setMobile] = useState(); + const [mobile, setMobile] = useState(""); const [age, setAge] = useState(""); const [role, setRole] = useState(""); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); - const [authReqResponse, setAuthReqResponse] = useState(); + const [authReqResponse, setAuthReqResponse] = useState(""); const [isSignUpMode, setIsSignUpMode] = useState(false); const toggleMode = () => { @@ -151,8 +151,9 @@ const AuthScreen = () => { > - + {/* */} + diff --git a/components/Authentication/UnauthorizedScreen.js b/components/Authentication/UnauthorizedScreen.js new file mode 100644 index 0000000..1a109a0 --- /dev/null +++ b/components/Authentication/UnauthorizedScreen.js @@ -0,0 +1,22 @@ +import React from "react"; +import { StyleSheet } from "react-native"; +import { ThemedText } from "@/components/common/ThemedText"; +import { ThemedView } from "@/components/common/ThemedView"; + +const UnauthorizedScreen = () => { + return ( + + You are unauthorized to access this page! + + ); +}; + +const styles = StyleSheet.create({ + container: { + flex: 1, + alignItems: "center", + justifyContent: "center", + }, +}); + +export default UnauthorizedScreen; diff --git a/screens/InventoryScreen/InventoryScreenContainer.js b/screens/InventoryScreen/InventoryScreenContainer.js index 9cd9082..a74e7fa 100644 --- a/screens/InventoryScreen/InventoryScreenContainer.js +++ b/screens/InventoryScreen/InventoryScreenContainer.js @@ -13,6 +13,7 @@ import { generateUniqueKey } from "@/utils/keyGenerator"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; import AuthContext from "@/components/Authentication/AuthProvider"; import AuthScreen from "@/components/Authentication/AuthScreen"; +import UnauthorizedScreen from "@/components/Authentication/UnauthorizedScreen"; const InventoryScreenContainer = () => { const [isLoading, setIsLoading] = useState(false); @@ -94,6 +95,18 @@ const InventoryScreenContainer = () => { const { user } = useContext(AuthContext); if (!user) return ; + if ( + user.staffDetails && + !( + user.staffDetails.role === "Manager" || + user.staffDetails.role === "Owner" || + !user.staffDetails.role || + user.staffDetails.role === "" + ) + ) { + return ; + } + if (isLoading) { return ; } diff --git a/screens/MenuScreen/MenuScreenContainer.js b/screens/MenuScreen/MenuScreenContainer.js index 2166d49..713327c 100644 --- a/screens/MenuScreen/MenuScreenContainer.js +++ b/screens/MenuScreen/MenuScreenContainer.js @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from "react"; +import React, { useState, useEffect, useContext } from "react"; import { collection, getDoc, @@ -14,6 +14,9 @@ import { db } from "@/firebase/firebaseConfig"; import MenuScreenView from "./MenuScreenView"; import { generateUniqueKey } from "@/utils/keyGenerator"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; +import AuthScreen from "@/components/Authentication/AuthScreen"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import UnauthorizedScreen from "@/components/Authentication/UnauthorizedScreen"; const MenuScreenContainer = () => { const [isLoading, setIsLoading] = useState(false); @@ -149,6 +152,21 @@ const MenuScreenContainer = () => { } }; + const { user } = useContext(AuthContext); + if (!user) return ; + + if ( + user.staffDetails && + !( + user.staffDetails.role === "Manager" || + user.staffDetails.role === "Owner" || + !user.staffDetails.role || + user.staffDetails.role === "" + ) + ) { + return ; + } + if (isLoading) { return ; } diff --git a/screens/ProfileScreen/ProfileScreen.js b/screens/ProfileScreen/ProfileScreen.js index 6f8c4fd..50ce8f5 100644 --- a/screens/ProfileScreen/ProfileScreen.js +++ b/screens/ProfileScreen/ProfileScreen.js @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useState, useContext } from "react"; import { View, Text, @@ -13,6 +13,9 @@ import { BlurView } from "expo-blur"; import Icon from "react-native-vector-icons/MaterialIcons"; import { useNavigation } from "@react-navigation/native"; import ApproveSignUpRequestsScreen from "@/components/Authentication/ApproveSignUpRequestsScreen"; +import AuthContext from "@/components/Authentication/AuthProvider"; +import AuthScreen from "@/components/Authentication/AuthScreen"; +import UnauthorizedScreen from "@/components/Authentication/UnauthorizedScreen"; const ProfileScreen = () => { const { width } = Dimensions.get("window"); @@ -58,6 +61,21 @@ const ProfileScreen = () => { // Implement edit functionality here }; + const { user } = useContext(AuthContext); + if (!user) return ; + + if ( + user.staffDetails && + !( + user.staffDetails.role === "Manager" || + user.staffDetails.role === "Owner" || + !user.staffDetails.role || + user.staffDetails.role === "" + ) + ) { + return ; + } + if (openApproveRequests) return ; return ( diff --git a/screens/StaffsScreen/StaffScreenView.js b/screens/StaffsScreen/StaffScreenView.js index c60997e..b9bcd01 100644 --- a/screens/StaffsScreen/StaffScreenView.js +++ b/screens/StaffsScreen/StaffScreenView.js @@ -31,13 +31,14 @@ const formSchema = [ inputMode: "default", type: "dropdown", options: [ - "Waiter", + "Manager", + // "Chef", "Cook", + "Waiter", + "Assistant", "Cleaner", - "Manager", "Helper", - "Assistant", - "Owner", + "Others", ], }, { diff --git a/screens/StaffsScreen/StaffsScreenContainer.js b/screens/StaffsScreen/StaffsScreenContainer.js index 89d8df2..61f07e4 100644 --- a/screens/StaffsScreen/StaffsScreenContainer.js +++ b/screens/StaffsScreen/StaffsScreenContainer.js @@ -13,6 +13,7 @@ import { generateUniqueKey } from "@/utils/keyGenerator"; import LoadingScreen from "@/components/LoadingScreen/LoadingScreen"; import AuthContext from "@/components/Authentication/AuthProvider"; import AuthScreen from "@/components/Authentication/AuthScreen"; +import UnauthorizedScreen from "@/components/Authentication/UnauthorizedScreen"; const StaffsScreenContainer = () => { const [isLoading, setIsLoading] = useState(false); @@ -92,6 +93,18 @@ const StaffsScreenContainer = () => { const { user } = useContext(AuthContext); if (!user) return ; + if ( + user.staffDetails && + !( + user.staffDetails.role === "Manager" || + user.staffDetails.role === "Owner" || + !user.staffDetails.role || + user.staffDetails.role === "" + ) + ) { + return ; + } + if (isLoading) { return ; } From 014d3086b13ead216574a4b17c58763e42b45f9e Mon Sep 17 00:00:00 2001 From: ThatNinjaGuy Date: Sat, 14 Sep 2024 15:36:33 +0530 Subject: [PATCH 9/9] Persistance enabled for android as well in addition to web --- components/Authentication/AuthProvider.js | 116 +++++++++++++++++----- firebase/firebaseConfig.js | 27 ++++- package-lock.json | 34 +++++++ package.json | 7 +- 4 files changed, 151 insertions(+), 33 deletions(-) diff --git a/components/Authentication/AuthProvider.js b/components/Authentication/AuthProvider.js index 15426e3..0721d68 100644 --- a/components/Authentication/AuthProvider.js +++ b/components/Authentication/AuthProvider.js @@ -1,51 +1,113 @@ import React, { createContext, useState, useEffect } from "react"; -import { onAuthStateChanged } from "firebase/auth"; -import { auth } from "@/firebase/firebaseConfig"; +import { onAuthStateChanged, signInWithCustomToken } from "firebase/auth"; +import { auth, db } from "@/firebase/firebaseConfig"; import { collection, getDocs, query, where } from "firebase/firestore"; -import { db } from "@/firebase/firebaseConfig"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Platform } from "react-native"; const AuthContext = createContext(); export const AuthProvider = ({ children }) => { const [user, setUser] = useState(null); + const [loading, setLoading] = useState(true); + + const storeAuthToken = async (user) => { + try { + const token = await user.getIdToken(); + if (Platform.OS === "web") { + localStorage.setItem("authToken", token); + } else { + await AsyncStorage.setItem("authToken", token); + } + } catch (error) { + console.error("Error storing auth token:", error); + } + }; + + const getStoredToken = async () => { + if (Platform.OS === "web") { + return localStorage.getItem("authToken"); + } else { + return await AsyncStorage.getItem("authToken"); + } + }; + + const fetchStaffDetails = async (user) => { + try { + const staffsRef = collection(db, "hotel-details/staff-details/staffs"); + const q = query(staffsRef, where("authId", "==", user.uid)); + const querySnapshot = await getDocs(q); + + const staffs = querySnapshot.docs.map((doc) => ({ + id: doc.id, + ...doc.data(), + })); + + if (staffs.length > 0) { + return { + ...user, + staffDetails: staffs[0], + }; + } + return user; + } catch (error) { + console.error("Error fetching staff details:", error); + return user; + } + }; useEffect(() => { - const subscriber = onAuthStateChanged(auth, async (user) => { - if (user) { + const initializeAuth = async () => { + const token = await getStoredToken(); + if (token) { try { - const staffsRef = collection( - db, - "hotel-details/staff-details/staffs" - ); - const q = query(staffsRef, where("authId", "==", user.uid)); - const querySnapshot = await getDocs(q); - - const staffs = querySnapshot.docs.map((doc) => ({ - id: doc.id, - ...doc.data(), - })); - if (staffs.length > 0) { - const updatedUser = { - ...user, - staffDetails: staffs[0], - }; - setUser(updatedUser); + await signInWithCustomToken(auth, token); + } catch (error) { + console.error("Error signing in with stored token:", error); + // Clear invalid token + if (Platform.OS === "web") { + localStorage.removeItem("authToken"); } else { - setUser(user); + await AsyncStorage.removeItem("authToken"); } - } catch (error) { - setUser(user); } + } + setLoading(false); + }; + + initializeAuth(); + + const subscriber = onAuthStateChanged(auth, async (firebaseUser) => { + if (firebaseUser) { + const updatedUser = await fetchStaffDetails(firebaseUser); + setUser(updatedUser); + await storeAuthToken(firebaseUser); } else { - setUser(user); + setUser(null); } }); return () => subscriber(); }, []); + const logout = async () => { + await auth.signOut(); + if (Platform.OS === "web") { + localStorage.removeItem("authToken"); + } else { + await AsyncStorage.removeItem("authToken"); + } + setUser(null); + }; + + if (loading) { + return null; // or a loading component + } + return ( - {children} + + {children} + ); }; diff --git a/firebase/firebaseConfig.js b/firebase/firebaseConfig.js index e47cc3a..7f49f21 100644 --- a/firebase/firebaseConfig.js +++ b/firebase/firebaseConfig.js @@ -1,6 +1,12 @@ import { initializeApp } from "firebase/app"; import { initializeFirestore } from "firebase/firestore"; -import { getAuth } from "firebase/auth"; +import { + getAuth, + initializeAuth, + getReactNativePersistence, +} from "firebase/auth"; +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { Platform } from "react-native"; const firebaseConfig = { apiKey: "AIzaSyAN-nxJtF6ROGWMjLboI4dEBKDNGnsMIWg", @@ -14,11 +20,26 @@ const firebaseConfig = { const app = initializeApp(firebaseConfig); -const auth = getAuth(app); +let auth; + +if (Platform.OS === "web") { + auth = getAuth(app); + // Set persistence for web + import("firebase/auth").then( + ({ browserLocalPersistence, setPersistence }) => { + setPersistence(auth, browserLocalPersistence); + } + ); +} else { + // Initialize auth with AsyncStorage persistence for React Native + auth = initializeAuth(app, { + persistence: getReactNativePersistence(AsyncStorage), + }); +} // Initialize Firestore with persistent local cache const db = initializeFirestore(app, { - experimentalForceLongPolling: true, // Optional: Use if you face network issues + experimentalForceLongPolling: true, synchronizeTabs: true, }); diff --git a/package-lock.json b/package-lock.json index 6bf9e4a..55e9329 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@dotlottie/react-player": "^1.6.19", "@expo/metro-config": "^0.18.9", "@expo/vector-icons": "^14.0.2", + "@react-native-async-storage/async-storage": "^1.24.0", "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", "@react-native-firebase/app": "^20.4.0", "@react-native-firebase/auth": "^20.4.0", @@ -5721,6 +5722,18 @@ "react": "^16.8 || ^17.0 || ^18.0" } }, + "node_modules/@react-native-async-storage/async-storage": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@react-native-async-storage/async-storage/-/async-storage-1.24.0.tgz", + "integrity": "sha512-W4/vbwUOYOjco0x3toB8QCr7EjIP6nE9G7o8PMguvvjYT5Awg09lyV4enACRx4s++PPulBiBSjL0KTFx2u0Z/g==", + "license": "MIT", + "dependencies": { + "merge-options": "^3.0.4" + }, + "peerDependencies": { + "react-native": "^0.0.0-0 || >=0.60 <1.0" + } + }, "node_modules/@react-native-community/cli": { "version": "13.6.9", "resolved": "https://registry.npmjs.org/@react-native-community/cli/-/cli-13.6.9.tgz", @@ -14190,6 +14203,15 @@ "node": ">=8" } }, + "node_modules/is-plain-obj": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", + "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-object": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", @@ -17385,6 +17407,18 @@ "resolved": "https://registry.npmjs.org/memory-cache/-/memory-cache-0.2.0.tgz", "integrity": "sha512-OcjA+jzjOYzKmKS6IQVALHLVz+rNTMPoJvCztFaZxwG14wtAW7VRZjwTQu06vKCYOxh4jVnik7ya0SXTB0W+xA==" }, + "node_modules/merge-options": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/merge-options/-/merge-options-3.0.4.tgz", + "integrity": "sha512-2Sug1+knBjkaMsMgf1ctR1Ujx+Ayku4EdJN4Z+C2+JzoeF7A3OZ9KM2GY0CpQS51NR61LTurMJrRKPhSs3ZRTQ==", + "license": "MIT", + "dependencies": { + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", diff --git a/package.json b/package.json index 29ea1bf..34a8382 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@dotlottie/react-player": "^1.6.19", "@expo/metro-config": "^0.18.9", "@expo/vector-icons": "^14.0.2", + "@react-native-async-storage/async-storage": "^1.24.0", "@react-native-community/datetimepicker": "github:react-native-community/datetimepicker", "@react-native-firebase/app": "^20.4.0", "@react-native-firebase/auth": "^20.4.0", @@ -26,9 +27,11 @@ "@react-navigation/drawer": "^6.7.2", "@react-navigation/native": "^6.1.18", "expo": "~51.0.20", + "expo-blur": "~13.0.2", "expo-constants": "~16.0.2", "expo-file-system": "~17.0.1", "expo-font": "~12.0.8", + "expo-linear-gradient": "~13.0.2", "expo-linking": "~6.3.1", "expo-print": "~13.0.1", "expo-router": "~3.5.18", @@ -60,9 +63,7 @@ "react-native-super-grid": "^6.0.1", "react-native-toast-message": "^2.2.0", "react-native-vector-icons": "^10.1.0", - "react-native-web": "^0.19.12", - "expo-linear-gradient": "~13.0.2", - "expo-blur": "~13.0.2" + "react-native-web": "^0.19.12" }, "devDependencies": { "@babel/core": "^7.20.0",