From 9b9f13eb6adb22c289bf8e528d9fcf53f73fe75c Mon Sep 17 00:00:00 2001 From: mudabbir-ahmad Date: Sun, 5 Apr 2026 18:07:45 +0100 Subject: [PATCH] Add comments for code structure clarity; enhance readability across multiple components --- App.js | 91 +++-- package.json | 3 - src/components/API/API.js | 32 ++ src/components/API/useLoad.js | 30 ++ src/components/UI/Button.js | 10 + .../gameplay/AdminCacheEditorView.js | 5 + src/components/gameplay/ClaimTimerView.js | 8 +- src/components/gameplay/PlayerMapView.js | 9 +- src/components/layout/BottomNavbar.js | 133 ++++---- src/components/layout/Screen.js | 18 +- src/components/layout/TopNavbar.js | 160 ++++----- src/components/screens/AdminHubScreen.js | 25 +- src/components/screens/CreateGameScreen.js | 25 +- src/components/screens/GameLobbyScreen.js | 25 +- src/components/screens/GameTypeScreen.js | 109 +++--- .../screens/JoinPrivateGameScreen.js | 5 + src/components/screens/LeaderboardScreen.js | 26 +- src/components/screens/LoginScreen.js | 10 +- src/components/screens/ManageGameScreen.js | 245 ++++++-------- .../screens/ManagePrivateGameScreen.js | 23 +- .../screens/ManageSubgroupsScreen.js | 25 +- src/components/screens/MapScreen.js | 268 +++++++-------- src/components/screens/RegisterScreen.js | 320 +++++++++--------- src/components/screens/TeamScreen.js | 25 +- src/hooks/SessionStore.js | 4 + src/hooks/dbLink.js | 23 +- src/hooks/useAuthHook.js | 61 ++-- src/hooks/useGameHook.js | 99 +++++- src/hooks/usePlayerGame.js | 49 +-- 29 files changed, 1020 insertions(+), 846 deletions(-) create mode 100644 src/components/API/API.js create mode 100644 src/components/API/useLoad.js diff --git a/App.js b/App.js index 0ae59de..6ba295b 100644 --- a/App.js +++ b/App.js @@ -17,65 +17,56 @@ import ManageSubgroupsScreen from "./src/components/screens/ManageSubgroupsScree const Stack = createNativeStackNavigator(); const AuthStack = () => ( - - - - + + + + ); const GameStack = () => ( - - - - - - - - - - - + + + + + + + + + + + ); const AdminStack = () => ( - - - - - - - - - + + + + + + + + + ); export const App = () => { - return ( - - - - - - - - ); + return ( + + + + + + + + ); }; export default App; - diff --git a/package.json b/package.json index ca70bae..f6df575 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,7 @@ "main": "node_modules/expo/AppEntry.js", "version": "1.0.0", "scripts": { - "api": "node server/server.js", - "api:standalone": "npm --prefix server start", "start": "expo start", - "start:api": "npm run api", "android": "expo run:android", "ios": "expo run:ios", "web": "expo start --web", diff --git a/src/components/API/API.js b/src/components/API/API.js new file mode 100644 index 0000000..074de50 --- /dev/null +++ b/src/components/API/API.js @@ -0,0 +1,32 @@ +const API = {}; + +API.get = (endpoint) => callFetch(endpoint, 'GET'); +API.post = (endpoint, data) => callFetch(endpoint, 'POST', data); +API.put = (endpoint, data) => callFetch(endpoint, 'PUT', data); +API.delete = (endpoint) => callFetch(endpoint, 'DELETE'); + +export default API; + +const callFetch = async (endpoint, method, dataObj = null) => { + // Build request object + let requestObj = {method: method}; // GET, POST, PUT or DELETE + if (dataObj) + requestObj = { + ...requestObj, + headers: {'Content-type': 'application/json'}, + body: JSON.stringify(dataObj), + }; + + // Call the fetch and process the return + try { + let result = null; + const response = await fetch(endpoint, requestObj); + if (response.status !== 204) result = await response.json(); + return response.status >= 200 && response.status < 300 + ? {isSuccess: true, result} + : {isSuccess: false, message: `${result.message}`}; + } catch (error) { + return {isSuccess: false, message: error.message}; + } +}; + diff --git a/src/components/API/useLoad.js b/src/components/API/useLoad.js new file mode 100644 index 0000000..5f3feef --- /dev/null +++ b/src/components/API/useLoad.js @@ -0,0 +1,30 @@ +import {useEffect, useState} from "react"; +import API from "./API"; + + +const useLoad = (loadEndpoint) => { + + // State ---------------------- + + const [records, setRecords] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + // Loader ---------------------- + const loadRecords = async (endpoint) => { + setIsLoading(true); + const response = await API.get(endpoint); + if (response.isSuccess) setRecords(response.result); + setIsLoading(false); + }; + + useEffect(() => { + loadRecords(loadEndpoint); + }, []); + + // return ---------------------- + + return [records, setRecords, isLoading, loadRecords]; +}; + +export default useLoad; + diff --git a/src/components/UI/Button.js b/src/components/UI/Button.js index 7c3bf4e..3c39ec2 100644 --- a/src/components/UI/Button.js +++ b/src/components/UI/Button.js @@ -1,6 +1,11 @@ import {Pressable, StyleSheet, Text, View} from "react-native"; export const Button = ({ label, icon, onClick, styleLabel, styleButton }) => { + // Initialisation ------------- + // State ---------------------- + // Handlers ------------------- + // View ----------------------- + return ( {icon ? icon : null} @@ -10,6 +15,11 @@ export const Button = ({ label, icon, onClick, styleLabel, styleButton }) => { }; export const ButtonTray = ({ children }) => { + // Initialisation ------------- + // State ---------------------- + // Handlers ------------------- + // View ----------------------- + return {children}; }; diff --git a/src/components/gameplay/AdminCacheEditorView.js b/src/components/gameplay/AdminCacheEditorView.js index 9635b6b..1880224 100644 --- a/src/components/gameplay/AdminCacheEditorView.js +++ b/src/components/gameplay/AdminCacheEditorView.js @@ -2,6 +2,11 @@ import React from 'react'; import MapView, {Circle, Marker} from 'react-native-maps'; const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache }) => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + if (!userLocation) { return null; } diff --git a/src/components/gameplay/ClaimTimerView.js b/src/components/gameplay/ClaimTimerView.js index 6c723bc..0b46031 100644 --- a/src/components/gameplay/ClaimTimerView.js +++ b/src/components/gameplay/ClaimTimerView.js @@ -2,8 +2,13 @@ import React, {useEffect, useState} from 'react'; import {StyleSheet, Text, View} from 'react-native'; const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => { +// Initialisation ------------- +// State ---------------------- + const [timeLeft, setTimeLeft] = useState(5); +// Handlers ------------------- + useEffect(() => { let timer; if (isClaiming && timeLeft > 0) { @@ -16,6 +21,8 @@ const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming }) => { return () => clearTimeout(timer); }, [isClaiming, timeLeft, cache, onClaimSuccess]); +// View ----------------------- + if (!isClaiming) { return null; } @@ -45,4 +52,3 @@ const styles = StyleSheet.create({ }); export default ClaimTimerView; - diff --git a/src/components/gameplay/PlayerMapView.js b/src/components/gameplay/PlayerMapView.js index 707901c..de9df1e 100644 --- a/src/components/gameplay/PlayerMapView.js +++ b/src/components/gameplay/PlayerMapView.js @@ -3,9 +3,12 @@ import MapView, {Circle} from 'react-native-maps'; import ClaimTimerView from './ClaimTimerView'; const PlayerMapView = ({ userLocation, visibleCache, isClaiming, onClaimSuccess }) => { - if (!userLocation) { - return null; - } +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + if (!userLocation) return null; return ( <> diff --git a/src/components/layout/BottomNavbar.js b/src/components/layout/BottomNavbar.js index 8ad987d..54473df 100644 --- a/src/components/layout/BottomNavbar.js +++ b/src/components/layout/BottomNavbar.js @@ -1,74 +1,79 @@ import {Pressable, StyleSheet, Text, View} from 'react-native'; const BottomNavbar = ({ navigation, routeName }) => { - const tabs = [ - { - label: 'Map', - route: 'MapScreen', - onPress: () => navigation.navigate('Game', { screen: 'MapScreen' }), - }, - { - label: 'Leaderboard', - route: 'LeaderboardScreen', - onPress: () => navigation.navigate('Game', { screen: 'LeaderboardScreen' }), - }, - { - label: 'Current Team', - route: 'TeamScreen', - onPress: () => navigation.navigate('Game', { screen: 'TeamScreen' }), - }, - ]; +// Initialisation ------------ - return ( - - {tabs.map((tab) => { - const isActive = routeName === tab.route; - return ( - - {tab.label} - - ); - })} - - ); + const tabs = [ + { + label: 'Map', + route: 'MapScreen', + onPress: () => navigation.navigate('Game', { screen: 'MapScreen' }), + }, + { + label: 'Leaderboard', + route: 'LeaderboardScreen', + onPress: () => navigation.navigate('Game', { screen: 'LeaderboardScreen' }), + }, + { + label: 'Current Team', + route: 'TeamScreen', + onPress: () => navigation.navigate('Game', { screen: 'TeamScreen' }), + }, + ]; + +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + {tabs.map((tab) => { + const isActive = routeName === tab.route; + return ( + + {tab.label} + + ); + })} + + ); }; const styles = StyleSheet.create({ - container: { - minHeight: 64, - borderTopWidth: 1, - borderTopColor: '#e5e7eb', - backgroundColor: '#ffffff', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-around', - paddingHorizontal: 8, - }, - tab: { - flex: 1, - alignItems: 'center', - justifyContent: 'center', - paddingVertical: 12, - borderRadius: 8, - marginHorizontal: 4, - }, - tabActive: { - backgroundColor: '#eff6ff', - }, - tabText: { - color: '#374151', - fontSize: 12, - fontWeight: '600', - textAlign: 'center', - }, - tabTextActive: { - color: '#2563eb', - }, + container: { + minHeight: 64, + borderTopWidth: 1, + borderTopColor: '#e5e7eb', + backgroundColor: '#ffffff', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-around', + paddingHorizontal: 8, + }, + tab: { + flex: 1, + alignItems: 'center', + justifyContent: 'center', + paddingVertical: 12, + borderRadius: 8, + marginHorizontal: 4, + }, + tabActive: { + backgroundColor: '#eff6ff', + }, + tabText: { + color: '#374151', + fontSize: 12, + fontWeight: '600', + textAlign: 'center', + }, + tabTextActive: { + color: '#2563eb', + }, }); export default BottomNavbar; - diff --git a/src/components/layout/Screen.js b/src/components/layout/Screen.js index 3c8a5f7..3382560 100644 --- a/src/components/layout/Screen.js +++ b/src/components/layout/Screen.js @@ -5,18 +5,18 @@ import TopNavbar from "./TopNavbar"; import BottomNavbar from "./BottomNavbar"; const Screen = ({ children, style }) => { + // Initialisation ------------- + const navigation = useNavigation(); const route = useRoute(); const titleMap = { - LoginScreen: "Login", - RegisterScreen: "Register", GameTypeScreen: "Game Type", JoinPrivateGameScreen: "Join Private Game", ManagePrivateGameScreen: "Manage Private Game", CreateGameScreen: "Create Game", ManageGameScreen: "Manage Game", - GameLobbyScreen: "Leaderboard", + GameLobbyScreen: "Game Lobby", LeaderboardScreen: "Leaderboard", TeamScreen: "Current Team", MapScreen: "Map", @@ -24,15 +24,21 @@ const Screen = ({ children, style }) => { ManageSubgroupsScreen: "Manage Subgroups", }; + // State ---------------------- + const routeName = route.name; - const showBack = routeName !== "LoginScreen" && navigation.canGoBack(); + const isAuthScreen = routeName === "LoginScreen" || routeName === "RegisterScreen"; + const showBack = !isAuthScreen && navigation.canGoBack(); const pageTitle = titleMap[routeName] || "Treasure Hunt"; + // Handlers ------------------- + // View ----------------------- + return ( - + {!isAuthScreen && } {children} - + {!isAuthScreen && } ); diff --git a/src/components/layout/TopNavbar.js b/src/components/layout/TopNavbar.js index 088517a..fffeb5e 100644 --- a/src/components/layout/TopNavbar.js +++ b/src/components/layout/TopNavbar.js @@ -1,90 +1,94 @@ import {Pressable, StyleSheet, Text, View} from 'react-native'; import {clearSession} from '../../hooks/SessionStore'; -const TopNavbar = ({ title, showBack, navigation }) => { - const routeName = navigation.getState()?.routes?.[navigation.getState().index]?.name; - const showLogout = routeName !== 'LoginScreen' && routeName !== 'RegisterScreen'; +const TopNavbar = ({title, showBack, navigation}) => { +// Initialisation ------------ - const handleBack = () => { - if (navigation.canGoBack()) { - navigation.goBack(); - } - }; + const routeName = navigation.getState()?.routes?.[navigation.getState().index]?.name; + const showLogout = routeName !== 'LoginScreen' && routeName !== 'RegisterScreen'; - const handleLogout = () => { - clearSession(); - navigation.reset({ - index: 0, - routes: [{ name: 'Auth', params: { screen: 'LoginScreen' } }], - }); - }; +// State ---------------------- +// Handlers ------------------- - return ( - - - {showBack ? ( - - {'<'} - - ) : null} - - {title} - - {showLogout ? ( - - Logout - - ) : null} - - - ); + const handleBack = () => { + if (navigation.canGoBack()) navigation.goBack(); + }; + + const handleLogout = () => { + clearSession(); + navigation.reset({ + index: 0, + routes: [{name: 'Auth', params: {screen: 'LoginScreen'}}], + }); + }; + +// View ----------------------- + + return ( + + + {showBack ? ( + + {'<'} + + ) : null} + + {title} + + {showLogout ? ( + + Logout + + ) : null} + + + ); }; const styles = StyleSheet.create({ - container: { - minHeight: 56, - borderBottomWidth: 1, - borderBottomColor: '#e5e7eb', - flexDirection: 'row', - alignItems: 'center', - justifyContent: 'space-between', - paddingHorizontal: 12, - backgroundColor: '#ffffff', - }, - leftSlot: { - width: 72, - alignItems: 'flex-start', - }, - rightSlot: { - width: 72, - alignItems: 'flex-end', - }, - iconButton: { - paddingVertical: 8, - paddingHorizontal: 10, - }, - iconText: { - fontSize: 20, - color: '#111827', - fontWeight: '700', - }, - title: { - flex: 1, - textAlign: 'center', - fontSize: 18, - fontWeight: '700', - color: '#111827', - }, - logoutButton: { - paddingVertical: 6, - paddingHorizontal: 8, - }, - logoutText: { - color: '#2563eb', - fontSize: 13, - fontWeight: '600', - }, + container: { + minHeight: 56, + borderBottomWidth: 1, + borderBottomColor: '#e5e7eb', + flexDirection: 'row', + alignItems: 'center', + justifyContent: 'space-between', + paddingHorizontal: 12, + backgroundColor: '#ffffff', + }, + leftSlot: { + width: 72, + alignItems: 'flex-start', + }, + rightSlot: { + width: 72, + alignItems: 'flex-end', + }, + iconButton: { + paddingVertical: 8, + paddingHorizontal: 10, + }, + iconText: { + fontSize: 20, + color: '#111827', + fontWeight: '700', + }, + title: { + flex: 1, + textAlign: 'center', + fontSize: 18, + fontWeight: '700', + color: '#111827', + }, + logoutButton: { + paddingVertical: 6, + paddingHorizontal: 8, + }, + logoutText: { + color: '#2563eb', + fontSize: 13, + fontWeight: '600', + }, }); export default TopNavbar; - diff --git a/src/components/screens/AdminHubScreen.js b/src/components/screens/AdminHubScreen.js index 1e68d6f..58822b6 100644 --- a/src/components/screens/AdminHubScreen.js +++ b/src/components/screens/AdminHubScreen.js @@ -2,17 +2,24 @@ import React from 'react'; import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; -const AdminHubScreen = () => ( - - Admin Hub - Admin hub setup goes here. - -); +const AdminHubScreen = () => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Admin Hub + Admin hub setup goes here. + + ); +}; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default AdminHubScreen; diff --git a/src/components/screens/CreateGameScreen.js b/src/components/screens/CreateGameScreen.js index d00cdae..d5f35a7 100644 --- a/src/components/screens/CreateGameScreen.js +++ b/src/components/screens/CreateGameScreen.js @@ -2,17 +2,24 @@ import React from 'react'; import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; -const CreateGameScreen = () => ( - - Create Game - Create game setup goes here. - -); +const CreateGameScreen = () => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Create Game + Create game setup goes here. + + ); +}; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default CreateGameScreen; diff --git a/src/components/screens/GameLobbyScreen.js b/src/components/screens/GameLobbyScreen.js index 6312cc6..1d726c7 100644 --- a/src/components/screens/GameLobbyScreen.js +++ b/src/components/screens/GameLobbyScreen.js @@ -2,17 +2,24 @@ import React from 'react'; import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; -const GameLobbyScreen = () => ( - - Game Lobby - Game lobby setup goes here. - -); +const GameLobbyScreen = () => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Game Lobby + Game lobby setup goes here. + + ); +}; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default GameLobbyScreen; diff --git a/src/components/screens/GameTypeScreen.js b/src/components/screens/GameTypeScreen.js index 8a3ef5d..f16a462 100644 --- a/src/components/screens/GameTypeScreen.js +++ b/src/components/screens/GameTypeScreen.js @@ -3,66 +3,71 @@ import {Pressable, StyleSheet, Text, View} from 'react-native'; import useGameHook from '../../hooks/useGameHook'; import Screen from '../layout/Screen'; -const GameTypeScreen = ({ navigation }) => { - const { getGameTypes } = useGameHook(); - const [gameTypes, setGameTypes] = useState([]); +const GameTypeScreen = ({navigation}) => { +// Initialisation ------------ - useEffect(() => { - let isMounted = true; + const {getGameTypes} = useGameHook(); - const load = async () => { - const types = await getGameTypes(); - if (isMounted) { - setGameTypes(types || []); - } - }; +// State ---------------------- - load(); + const [gameTypes, setGameTypes] = useState([]); - return () => { - isMounted = false; - }; - }, [getGameTypes]); +// Handlers ------------------- - return ( - - Select Game Type - - {gameTypes && gameTypes.map((type) => ( - navigation.navigate('GameLobbyScreen')}> - {type} - - ))} - - - ); + useEffect(() => { + let isMounted = true; + const load = async () => { + const types = await getGameTypes(); + if (isMounted) setGameTypes(types || []); + }; + load(); + return () => { isMounted = false; }; + }, []); + +// View ----------------------- + + return ( + + Select Game Type + + {gameTypes && gameTypes.map((type) => ( + navigation.navigate('GameLobbyScreen')} + > + {type} + + ))} + + + ); }; const styles = StyleSheet.create({ - container: { - justifyContent: 'center', - }, - content: { - gap: 12, - flex: 1, - }, - title: { - fontSize: 24, - fontWeight: 'bold', - textAlign: 'center', - marginBottom: 20, - }, - button: { - backgroundColor: '#2563eb', - borderRadius: 8, - paddingVertical: 12, - alignItems: 'center', - }, - buttonText: { - color: '#ffffff', - fontWeight: '600', - }, + container: { + justifyContent: 'center', + }, + content: { + gap: 12, + flex: 1, + }, + title: { + fontSize: 24, + fontWeight: 'bold', + textAlign: 'center', + marginBottom: 20, + }, + button: { + backgroundColor: '#2563eb', + borderRadius: 8, + paddingVertical: 12, + alignItems: 'center', + }, + buttonText: { + color: '#ffffff', + fontWeight: '600', + }, }); export default GameTypeScreen; - diff --git a/src/components/screens/JoinPrivateGameScreen.js b/src/components/screens/JoinPrivateGameScreen.js index 4579833..e2c90e1 100644 --- a/src/components/screens/JoinPrivateGameScreen.js +++ b/src/components/screens/JoinPrivateGameScreen.js @@ -3,6 +3,11 @@ import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; const JoinPrivateGameScreen = () => { + // Initialisation ------------- + // State ---------------------- + // Handlers ------------------- + // View ----------------------- + return ( Join Private Game diff --git a/src/components/screens/LeaderboardScreen.js b/src/components/screens/LeaderboardScreen.js index 79ff6a4..4ed2214 100644 --- a/src/components/screens/LeaderboardScreen.js +++ b/src/components/screens/LeaderboardScreen.js @@ -2,18 +2,24 @@ import React from 'react'; import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; -const LeaderboardScreen = () => ( - - Leaderboard - Leaderboard view goes here. - -); +const LeaderboardScreen = () => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Leaderboard + Leaderboard view goes here. + + ); +}; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default LeaderboardScreen; - diff --git a/src/components/screens/LoginScreen.js b/src/components/screens/LoginScreen.js index 06172b3..a764f8d 100644 --- a/src/components/screens/LoginScreen.js +++ b/src/components/screens/LoginScreen.js @@ -5,9 +5,16 @@ import Screen from '../layout/Screen'; import {Button, ButtonTray} from '../UI/Button'; const LoginScreen = ({ navigation }) => { +// Initialisation -------------> + const { email, password, error, isLoading, setEmail, setPassword, login } = useAuthHook(); + +// State ---------------------- + const [submitting, setSubmitting] = useState(false); +// Handlers ------------------- + const handleLogin = async () => { setSubmitting(true); const user = await login(); @@ -22,6 +29,8 @@ const LoginScreen = ({ navigation }) => { navigation.reset({ index: 0, routes: [{ name: 'Game' }] }); }; +// View ----------------------- + return ( @@ -107,4 +116,3 @@ const styles = StyleSheet.create({ }); export default LoginScreen; - diff --git a/src/components/screens/ManageGameScreen.js b/src/components/screens/ManageGameScreen.js index 3bb235b..01201c1 100644 --- a/src/components/screens/ManageGameScreen.js +++ b/src/components/screens/ManageGameScreen.js @@ -7,163 +7,138 @@ import useGameHook from '../../hooks/useGameHook'; import {getSession} from '../../hooks/SessionStore'; const ManageGameScreen = () => { - const session = getSession(); - const { getCaches, upsertCache } = useGameHook(); - const [userLocation, setUserLocation] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(''); - const [cacheRecords, setCacheRecords] = useState([]); +// Initialisation ------------ - const loadCaches = useCallback(async () => { - if (!session.currentGid) { - setCacheRecords([]); - return; - } - const rows = await getCaches(session.currentGid, null); - setCacheRecords(rows || []); - }, [getCaches, session.currentGid]); + const session = getSession(); + const {getCaches, upsertCache} = useGameHook(); - useEffect(() => { - loadCaches(); - }, [loadCaches]); +// State ---------------------- - useEffect(() => { - let locationSub; + const [userLocation, setUserLocation] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(''); + const [cacheRecords, setCacheRecords] = useState([]); - const start = async () => { - const { status } = await Location.requestForegroundPermissionsAsync(); - if (status !== 'granted') { - setError('Location permission denied'); - setLoading(false); - return; - } +// Handlers ------------------- - const current = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced }); - setUserLocation({ latitude: current.coords.latitude, longitude: current.coords.longitude }); + const loadCaches = useCallback(async () => { + if (!session.currentGid) { + setCacheRecords([]); + return; + } + const rows = await getCaches(session.currentGid, null); + setCacheRecords(rows || []); + }, [session.currentGid]); - locationSub = await Location.watchPositionAsync( - { - accuracy: Location.Accuracy.Balanced, - distanceInterval: 1, - timeInterval: 1000, - }, - (next) => { - setUserLocation({ latitude: next.coords.latitude, longitude: next.coords.longitude }); - }, - ); + useEffect(() => { loadCaches(); }, [loadCaches]); - setLoading(false); + useEffect(() => { + let locationSub; + const start = async () => { + const {status} = await Location.requestForegroundPermissionsAsync(); + if (status !== 'granted') { + setError('Location permission denied'); + setLoading(false); + return; + } + const current = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Balanced}); + setUserLocation({latitude: current.coords.latitude, longitude: current.coords.longitude}); + + locationSub = await Location.watchPositionAsync( + {accuracy: Location.Accuracy.Balanced, distanceInterval: 1, timeInterval: 1000}, + (next) => { + setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}); + }, + ); + setLoading(false); + }; + start(); + return () => { if (locationSub) locationSub.remove(); }; + }, []); + + const handleAddCache = async (coordinate) => { + if (!session.currentGid) return; + await upsertCache({ + gid: session.currentGid, + latitude: coordinate.latitude, + longitude: coordinate.longitude, + radius: 20, + clue: `Cache ${cacheRecords.length + 1}`, + subgroupId: 1, + }); + await loadCaches(); }; - start(); - - return () => { - if (locationSub) { - locationSub.remove(); - } + const handleMoveCache = async (cacheId, coordinate) => { + if (!session.currentGid) return; + const current = cacheRecords.find((cache) => cache.id === cacheId); + if (!current) return; + await upsertCache({ + gid: session.currentGid, + cacheId, + latitude: coordinate.latitude, + longitude: coordinate.longitude, + radius: current.radius, + clue: current.clue, + subgroupId: current.subgroupId, + }); + await loadCaches(); }; - }, []); - const handleAddCache = async (coordinate) => { +// View ----------------------- + if (!session.currentGid) { - return; + return ( + + Create or join a game first. + + ); } - await upsertCache({ - gid: session.currentGid, - latitude: coordinate.latitude, - longitude: coordinate.longitude, - radius: 20, - clue: `Cache ${cacheRecords.length + 1}`, - subgroupId: 1, - }); - await loadCaches(); - }; - const handleMoveCache = async (cacheId, coordinate) => { - if (!session.currentGid) { - return; + if (!session.isAcceptedAdmin) { + return ( + + Only admins can edit cache locations. + + ); } - const current = cacheRecords.find((cache) => cache.id === cacheId); - if (!current) { - return; + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + {error} + + ); } - await upsertCache({ - gid: session.currentGid, - cacheId, - latitude: coordinate.latitude, - longitude: coordinate.longitude, - radius: current.radius, - clue: current.clue, - subgroupId: current.subgroupId, - }); - await loadCaches(); - }; - if (!session.currentGid) { return ( - - Create or join a game first. - + + + + + ); - } - - if (!session.isAcceptedAdmin) { - return ( - - Only admins can edit cache locations. - - ); - } - - if (loading) { - return ( - - - - ); - } - - if (error) { - return ( - - {error} - - ); - } - - return ( - - - - - - ); }; const styles = StyleSheet.create({ - container: { - padding: 0, - }, - mapWrap: { - flex: 1, - }, - center: { - justifyContent: 'center', - alignItems: 'center', - }, - body: { - color: '#4b5563', - fontSize: 15, - }, - error: { - color: '#dc2626', - fontSize: 15, - }, + container: {padding: 0}, + mapWrap: {flex: 1}, + center: {justifyContent: 'center', alignItems: 'center'}, + body: {color: '#4b5563', fontSize: 15}, + error: {color: '#dc2626', fontSize: 15}, }); export default ManageGameScreen; diff --git a/src/components/screens/ManagePrivateGameScreen.js b/src/components/screens/ManagePrivateGameScreen.js index 3de6a59..f8b1f24 100644 --- a/src/components/screens/ManagePrivateGameScreen.js +++ b/src/components/screens/ManagePrivateGameScreen.js @@ -3,18 +3,23 @@ import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; const ManagePrivateGameScreen = () => { - return ( - - Manage Private Game - Manage private game setup goes here. - - ); +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Manage Private Game + Manage private game setup goes here. + + ); }; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default ManagePrivateGameScreen; diff --git a/src/components/screens/ManageSubgroupsScreen.js b/src/components/screens/ManageSubgroupsScreen.js index 3cdde37..1df2523 100644 --- a/src/components/screens/ManageSubgroupsScreen.js +++ b/src/components/screens/ManageSubgroupsScreen.js @@ -2,17 +2,24 @@ import React from 'react'; import {StyleSheet, Text} from 'react-native'; import Screen from '../layout/Screen'; -const ManageSubgroupsScreen = () => ( - - Manage Subgroups - Manage subgroups setup goes here. - -); +const ManageSubgroupsScreen = () => { +// Initialisation ------------- +// State ---------------------- +// Handlers ------------------- +// View ----------------------- + + return ( + + Manage Subgroups + Manage subgroups setup goes here. + + ); +}; const styles = StyleSheet.create({ - container: { justifyContent: 'center', alignItems: 'center' }, - title: { fontSize: 24, fontWeight: 'bold' }, - body: { color: '#4b5563', fontSize: 15, marginTop: 10 }, + container: {justifyContent: 'center', alignItems: 'center'}, + title: {fontSize: 24, fontWeight: 'bold'}, + body: {color: '#4b5563', fontSize: 15, marginTop: 10}, }); export default ManageSubgroupsScreen; diff --git a/src/components/screens/MapScreen.js b/src/components/screens/MapScreen.js index 8e1704b..97aaeb2 100644 --- a/src/components/screens/MapScreen.js +++ b/src/components/screens/MapScreen.js @@ -9,175 +9,153 @@ import usePlayerGame from '../../hooks/usePlayerGame'; import {getSession} from '../../hooks/SessionStore'; import Cache from '../../models/Cache'; -const toHeading = ({ x, y }) => { - const angle = Math.atan2(y, x) * (180 / Math.PI); - return angle < 0 ? angle + 360 : angle; +const toHeading = ({x, y}) => { + const angle = Math.atan2(y, x) * (180 / Math.PI); + return angle < 0 ? angle + 360 : angle; }; const MapScreen = () => { - const session = getSession(); - const { getCaches, claimCache } = useGameHook(); - const [userLocation, setUserLocation] = useState(null); - const [heading, setHeading] = useState(null); - const [loading, setLoading] = useState(true); - const [cacheRecords, setCacheRecords] = useState([]); - const [error, setError] = useState(''); +// Initialisation ------------ - const activeCaches = useMemo(() => ( - cacheRecords.map( - (cache) => new Cache( - cache.id, - cache.coordinates.latitude, - cache.coordinates.longitude, - cache.radius, - cache.clue, - cache.groupId, - cache.subgroupId, - ), - ) - ), [cacheRecords]); + const session = getSession(); + const {getCaches, claimCache} = useGameHook(); - const subgroupFilter = session.currentSGid; - const { visibleCache, isClaiming, setIsClaiming } = usePlayerGame(userLocation, heading, activeCaches); +// State ---------------------- - const loadCaches = useCallback(async () => { - if (!session.currentGid) { - setCacheRecords([]); - return; - } - const rows = await getCaches(session.currentGid, subgroupFilter); - setCacheRecords(rows || []); - }, [getCaches, session.currentGid, subgroupFilter]); + const [userLocation, setUserLocation] = useState(null); + const [heading, setHeading] = useState(null); + const [loading, setLoading] = useState(true); + const [cacheRecords, setCacheRecords] = useState([]); + const [error, setError] = useState(''); - useEffect(() => { - loadCaches(); - }, [loadCaches]); + const activeCaches = useMemo(() => ( + cacheRecords.map((cache) => new Cache( + cache.id, + cache.coordinates.latitude, + cache.coordinates.longitude, + cache.radius, + cache.clue, + cache.groupId, + cache.subgroupId, + )) + ), [cacheRecords]); - useEffect(() => { - let locationSub; - let headingSub; + const subgroupFilter = session.currentSGid; + const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(userLocation, heading, activeCaches); - const start = async () => { - const { status } = await Location.requestForegroundPermissionsAsync(); - if (status !== 'granted') { - setError('Location permission denied'); - setLoading(false); - return; - } +// Handlers ------------------- - const current = await Location.getCurrentPositionAsync({ accuracy: Location.Accuracy.Balanced }); - setUserLocation({ latitude: current.coords.latitude, longitude: current.coords.longitude }); + const loadCaches = useCallback(async () => { + if (!session.currentGid) { + setCacheRecords([]); + return; + } + const rows = await getCaches(session.currentGid, subgroupFilter); + setCacheRecords(rows || []); + }, [session.currentGid, subgroupFilter]); - locationSub = await Location.watchPositionAsync( - { - accuracy: Location.Accuracy.Balanced, - distanceInterval: 1, - timeInterval: 1000, - }, - (next) => { - setUserLocation({ latitude: next.coords.latitude, longitude: next.coords.longitude }); - }, - ); + useEffect(() => { loadCaches(); }, [loadCaches]); - Magnetometer.setUpdateInterval(500); - headingSub = Magnetometer.addListener((data) => { - setHeading(toHeading(data)); - }); + useEffect(() => { + let locationSub; + let headingSub; - setLoading(false); + const start = async () => { + const {status} = await Location.requestForegroundPermissionsAsync(); + if (status !== 'granted') { + setError('Location permission denied'); + setLoading(false); + return; + } + const current = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Balanced}); + setUserLocation({latitude: current.coords.latitude, longitude: current.coords.longitude}); + + locationSub = await Location.watchPositionAsync( + {accuracy: Location.Accuracy.Balanced, distanceInterval: 1, timeInterval: 1000}, + (next) => { + setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}); + }, + ); + + Magnetometer.setUpdateInterval(500); + headingSub = Magnetometer.addListener((data) => { setHeading(toHeading(data)); }); + setLoading(false); + }; + + start(); + + return () => { + if (locationSub) locationSub.remove(); + if (headingSub) headingSub.remove(); + }; + }, []); + + const handleClaim = async (cacheId) => { + if (!session.currentGid) return; + await claimCache({ + gid: session.currentGid, + cacheId, + uid: session.currentUid, + tid: session.currentTid, + }); + setIsClaiming(false); + await loadCaches(); }; - start(); +// View ----------------------- - return () => { - if (locationSub) { - locationSub.remove(); - } - if (headingSub) { - headingSub.remove(); - } - }; - }, []); - - const handleClaim = async (cacheId) => { if (!session.currentGid) { - return; + return ( + + Join or create a game to use map features. + + ); + } + + if (session.isAcceptedAdmin) { + return ( + + Admins manage caches from Manage Game. + + ); + } + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + {error} + + ); } - await claimCache({ - gid: session.currentGid, - cacheId, - uid: session.currentUid, - tid: session.currentTid, - }); - setIsClaiming(false); - await loadCaches(); - }; - if (!session.currentGid) { return ( - - Join or create a game to use map features. - + + + + + ); - } - - if (session.isAcceptedAdmin) { - return ( - - Admins manage caches from Manage Game. - - ); - } - - if (loading) { - return ( - - - - ); - } - - if (error) { - return ( - - {error} - - ); - } - - return ( - - - - - - ); }; const styles = StyleSheet.create({ - container: { - padding: 0, - }, - mapWrap: { - flex: 1, - }, - center: { - justifyContent: 'center', - alignItems: 'center', - }, - body: { - color: '#4b5563', - fontSize: 15, - }, - error: { - color: '#dc2626', - fontSize: 15, - }, + container: {padding: 0}, + mapWrap: {flex: 1}, + center: {justifyContent: 'center', alignItems: 'center'}, + body: {color: '#4b5563', fontSize: 15}, + error: {color: '#dc2626', fontSize: 15}, }); export default MapScreen; diff --git a/src/components/screens/RegisterScreen.js b/src/components/screens/RegisterScreen.js index a575459..7883d85 100644 --- a/src/components/screens/RegisterScreen.js +++ b/src/components/screens/RegisterScreen.js @@ -4,175 +4,177 @@ import useAuthHook from '../../hooks/useAuthHook'; import Screen from '../layout/Screen'; import {Button, ButtonTray} from '../UI/Button'; -const RegisterScreen = ({ navigation }) => { - const { - username, - email, - password, - confirmPassword, - accountType, - error, - isLoading, - setUsername, - setEmail, - setPassword, - setConfirmPassword, - setAccountType, - register, - } = useAuthHook(); - const [submitting, setSubmitting] = useState(false); +const RegisterScreen = ({navigation}) => { +// Initialisation ------------ - const handleRegister = async () => { - setSubmitting(true); - const user = await register(); - setSubmitting(false); - if (!user) { - return; - } - if (user.IsAcceptedAdmin) { - navigation.reset({ index: 0, routes: [{ name: 'Admin' }] }); - return; - } - navigation.reset({ index: 0, routes: [{ name: 'Game' }] }); - }; + const { + username, email, password, confirmPassword, accountType, + error, isLoading, + setUsername, setEmail, setPassword, setConfirmPassword, setAccountType, + register, + } = useAuthHook(); - return ( - - - Create Account +// State ---------------------- - - - - + const [submitting, setSubmitting] = useState(false); - - setAccountType('Individual')} - disabled={isLoading} - > - Individual - - setAccountType('Business/School')} - disabled={isLoading} - > - Business - - +// Handlers ------------------- - {error ? {error} : null} + const handleRegister = async () => { + setSubmitting(true); + const user = await register(); + setSubmitting(false); + if (!user) return; + if (user.IsAcceptedAdmin) { + navigation.reset({index: 0, routes: [{name: 'Admin'}]}); + return; + } + navigation.reset({index: 0, routes: [{name: 'Game'}]}); + }; - -