fixed issues with merge + Global logic

This commit is contained in:
bobbert committed 2026-04-09 18:18:18 +01:00
1 parent b9b11463cc
commit 20a2edbbda
18 files changed
+386 -346

No files matched your search

+5 -19
View File
@@ -12,7 +12,6 @@ import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
import DepartmentSettingsScreen from "./src/components/screens/DepartmentSettingsScreen"; import DepartmentSettingsScreen from "./src/components/screens/DepartmentSettingsScreen";
import PlayersScreen from "./src/components/screens/PlayersScreen"; import PlayersScreen from "./src/components/screens/PlayersScreen";
import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen"; import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen";
import GameSelectionScreen from "./src/components/screens/GameSelectionScreen";
import GlobalEventsScreen from "./src/components/screens/global/GlobalEventsScreen"; import GlobalEventsScreen from "./src/components/screens/global/GlobalEventsScreen";
import GlobalMapScreen from "./src/components/screens/global/GlobalMapScreen"; import GlobalMapScreen from "./src/components/screens/global/GlobalMapScreen";
import GlobalCacheViewScreen from "./src/components/screens/global/GlobalCacheViewScreen"; import GlobalCacheViewScreen from "./src/components/screens/global/GlobalCacheViewScreen";
@@ -26,7 +25,7 @@ const AdminStackNav = createNativeStackNavigator();
const screenOptions = { const screenOptions = {
headerShown: false, headerShown: false,
contentStyle: { backgroundColor: "#ffffff" }, contentStyle: { backgroundColor: "#1e1e2e" },
}; };
const AuthStack = () => ( const AuthStack = () => (
@@ -40,10 +39,6 @@ const AuthStack = () => (
const GameStack = () => ( const GameStack = () => (
<GameStackNav.Navigator screenOptions={screenOptions}> <GameStackNav.Navigator screenOptions={screenOptions}>
<GameStackNav.Screen
name="GameSelectionScreen"
component={GameSelectionScreen}
/>
<GameStackNav.Screen name="MapScreen" component={MapScreen} /> <GameStackNav.Screen name="MapScreen" component={MapScreen} />
<GameStackNav.Screen name="TeamScreen" component={TeamScreen} /> <GameStackNav.Screen name="TeamScreen" component={TeamScreen} />
<GameStackNav.Screen <GameStackNav.Screen
@@ -73,14 +68,14 @@ const GameStack = () => (
const AdminStack = () => ( const AdminStack = () => (
<AdminStackNav.Navigator screenOptions={screenOptions}> <AdminStackNav.Navigator screenOptions={screenOptions}>
<AdminStackNav.Screen
name="GameSelectionScreen"
component={GameSelectionScreen}
/>
<AdminStackNav.Screen <AdminStackNav.Screen
name="GameSettingsScreen" name="GameSettingsScreen"
component={GameSettingsScreen} component={GameSettingsScreen}
/> />
<AdminStackNav.Screen
name="DepartmentSettingsScreen"
component={DepartmentSettingsScreen}
/>
<AdminStackNav.Screen name="MapScreen" component={MapScreen} /> <AdminStackNav.Screen name="MapScreen" component={MapScreen} />
<AdminStackNav.Screen <AdminStackNav.Screen
name="LeaderboardScreen" name="LeaderboardScreen"
@@ -106,15 +101,6 @@ const AdminStack = () => (
component={GlobalLeaderboardScreen} component={GlobalLeaderboardScreen}
/> />
</AdminStackNav.Navigator> </AdminStackNav.Navigator>
<AdminStackNav.Navigator screenOptions={screenOptions}>
<AdminStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
<AdminStackNav.Screen name="DepartmentSettingsScreen" component={DepartmentSettingsScreen}/>
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
<AdminStackNav.Screen name="ExpandedMapScreen" component={ExpandedMapScreen}/>
<AdminStackNav.Screen name="TeamScreen" component={TeamScreen}/>
</AdminStackNav.Navigator>
); );
export const App = () => { export const App = () => {
Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

+1
View File
@@ -36,6 +36,7 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
flexDirection: "row", flexDirection: "row",
gap: 5, gap: 5,
marginHorizontal: 4,
}, },
label: { label: {
fontSize: 16, fontSize: 16,
+75
View File
@@ -0,0 +1,75 @@
import {StyleSheet, TextInput, View} from 'react-native';
import {Button} from './Button';
const CodeJoinRow = ({
value,
onChange,
placeholder,
buttonLabel = 'Join',
onSubmit,
disabled = false,
style,
}) => {
// Initialisation -------------
// State ----------------------
// Handlers -------------------
// View -----------------------
return (
<View style={[styles.inputRow, style]}>
<TextInput
style={[styles.codeInput, disabled && styles.inputDisabled]}
placeholder={placeholder}
placeholderTextColor="#9ca3af"
value={value}
onChangeText={(text) => onChange(text.toUpperCase())}
autoCapitalize="characters"
editable={!disabled}
/>
<Button
label={buttonLabel}
onClick={onSubmit}
styleButton={styles.joinButton}
styleLabel={styles.joinLabel}
disabled={disabled}
/>
</View>
);
};
const styles = StyleSheet.create({
inputRow: {
flexDirection: 'row',
gap: 10,
marginBottom: 15,
width: '100%',
paddingHorizontal: 20,
},
codeInput: {
flex: 1,
borderWidth: 1,
borderColor: '#45475a',
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 10,
fontSize: 16,
color: '#cdd6f4',
backgroundColor: '#313244',
},
inputDisabled: {
opacity: 0.5,
},
joinButton: {
backgroundColor: '#bd93f9',
borderColor: '#bd93f9',
flex: 0,
paddingHorizontal: 20,
},
joinLabel: {
color: '#1e1e2e',
fontWeight: '600',
},
});
export default CodeJoinRow;
+78 -191
View File
@@ -1,84 +1,63 @@
import { Alert, Pressable, StyleSheet, Text, View } from "react-native"; import {Alert, Image, Pressable, StyleSheet, Text, View} from 'react-native';
import { useSafeAreaInsets } from "react-native-safe-area-context"; import {useSafeAreaInsets} from 'react-native-safe-area-context';
import { clearGameSession, getSession } from "../../hooks/SessionStore"; import {clearGameSession, clearGlobalSession, getSession, setSessionMode} from '../../hooks/SessionStore';
import API, { API_BASE_URL } from "../API/API"; import API, {API_BASE_URL} from '../API/API';
import {GAME_MODE} from '../../utils/gameConstants';
const iconMap = {
Settings: require('../../../assets/icons/settings-icon.png'),
Events: require('../../../assets/icons/events-icon.png'),
Map: require('../../../assets/icons/Map-icon.png'),
Leaderboard: require('../../../assets/icons/Leaderboard-icon.png'),
Team: require('../../../assets/icons/Teams-icon.png'),
Players: require('../../../assets/icons/Teams-icon.png'),
};
const BottomNavbar = ({ navigation, routeName }) => {
// Initialisation ------------
const BottomNavbar = ({navigation, routeName, routeParams}) => { const BottomNavbar = ({navigation, routeName, routeParams}) => {
// Initialisation ------------ // Initialisation ------------
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const session = getSession(); const session = getSession();
const inGame = Boolean(session.currentGid);
const isGlobalMode = !session.isBusiness && session.currentGameMode === GAME_MODE.GLOBAL;
const adminTabs = [
{
label: "Settings",
route: "GameSettingsScreen",
onPress: () => navigation.navigate("GameSettingsScreen"),
},
{
label: "Map",
route: "MapScreen",
onPress: () => navigation.navigate("MapScreen"),
},
{
label: "Players",
route: "PlayersScreen",
onPress: () => navigation.navigate("PlayersScreen"),
},
{
label: "Leaderboard",
route: "LeaderboardScreen",
onPress: () => navigation.navigate("LeaderboardScreen"),
},
{
label: "Games",
route: "GameSelectionScreen",
onPress: () => navigation.navigate("GameSelectionScreen"),
},
{
label: "Global",
route: "GlobalEventsScreen",
onPress: () => navigation.navigate("GlobalEventsScreen"),
},
];
const playerTabs = [
{
label: "Team",
route: "TeamScreen",
onPress: () => navigation.navigate("TeamScreen"),
},
{
label: "Map",
route: "MapScreen",
onPress: () => navigation.navigate("MapScreen"),
},
{
label: "Leaderboard",
route: "LeaderboardScreen",
onPress: () => navigation.navigate("LeaderboardScreen"),
},
{
label: "Games",
route: "GameSelectionScreen",
onPress: () => navigation.navigate("GameSelectionScreen"),
},
{
label: "Global",
route: "GlobalEventsScreen",
onPress: () => navigation.navigate("GlobalEventsScreen"),
},
{ label: "Leave", route: "__leave__", onPress: () => handleLeaveGame() },
];
const tabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
const selectedDepartment = routeName === 'DepartmentSettingsScreen' && routeParams?.SGid const selectedDepartment = routeName === 'DepartmentSettingsScreen' && routeParams?.SGid
? {SGid: routeParams.SGid, SubGroupName: routeParams.SubGroupName || routeParams.selectedDepartmentName || 'Department'} ? {
SGid: routeParams.SGid,
SubGroupName: routeParams.SubGroupName || routeParams.selectedDepartmentName || 'Department',
}
: null; : null;
// State ----------------------
// Handlers -------------------
const handleLeaveGame = () => {
Alert.alert('Leave Game', 'Are you sure you want to leave this game?', [
{text: 'Cancel', style: 'cancel'},
{
text: 'Leave',
style: 'destructive',
onPress: async () => {
const currentSession = getSession();
const endpoint = `${API_BASE_URL}/subgroup-members?Uid=${currentSession.currentUid}&Gid=${currentSession.currentGid}`;
const response = await API.get(endpoint);
if (response.isSuccess && response.result.length > 0) {
await API.delete(`${API_BASE_URL}/subgroup-members/${response.result[0].id}`);
}
clearGameSession();
const rootNav = navigation.getParent() || navigation;
rootNav.reset({index: 0, routes: [{name: 'Game'}]});
},
},
]);
};
const handleExitGlobal = () => {
clearGlobalSession();
setSessionMode(session.currentGid ? GAME_MODE.REGULAR : null);
navigation.navigate('MapScreen');
};
const adminTabs = [ const adminTabs = [
{label: 'Settings', route: 'GameSettingsScreen', onPress: () => navigation.navigate('GameSettingsScreen')}, {label: 'Settings', route: 'GameSettingsScreen', onPress: () => navigation.navigate('GameSettingsScreen')},
{ {
@@ -101,149 +80,51 @@ const BottomNavbar = ({navigation, routeName, routeParams}) => {
}, },
]; ];
// Only show the Leave tab when the player is actually in a game
const inGame = Boolean(session.currentGid);
const playerTabs = [ const playerTabs = [
{label: 'Team', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')}, {label: 'Team', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')},
{label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')}, {label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')},
{label: 'Leaderboard', route: 'LeaderboardScreen', onPress: () => navigation.navigate('LeaderboardScreen')}, {label: 'Leaderboard', route: 'LeaderboardScreen', onPress: () => navigation.navigate('LeaderboardScreen')},
...(inGame ? [{label: 'Leave', route: '__leave__', onPress: () => handleLeaveGame()}] : []), ...(inGame ? [{label: 'Leave', route: '__leave__', onPress: handleLeaveGame}] : []),
]; ];
const tabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
const pendingTabs = [ const pendingTabs = [
{label: 'Pending', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')}, {label: 'Pending', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')},
...(inGame ? [{label: 'Leave', route: '__leave__', onPress: () => handleLeaveGame()}] : []), ...(inGame ? [{label: 'Leave', route: '__leave__', onPress: handleLeaveGame}] : []),
]; ];
const visibleTabs = (session.isPendingAdmin && !session.isAcceptedAdmin) ? pendingTabs : tabs;
// State ---------------------- const globalTabs = [
// Handlers ------------------- {label: 'Events', route: 'GlobalEventsScreen', onPress: () => navigation.navigate('GlobalEventsScreen')},
{label: 'Map', route: 'GlobalMapScreen', onPress: () => navigation.navigate('GlobalMapScreen')},
{label: 'Leaderboard', route: 'GlobalLeaderboardScreen', onPress: () => navigation.navigate('GlobalLeaderboardScreen')},
{label: 'Exit', route: '__exit_global__', onPress: handleExitGlobal},
];
const handleLeaveGame = () => { let visibleTabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
Alert.alert("Leave Game", "Are you sure you want to leave this game?", [ if (session.isPendingAdmin && !session.isAcceptedAdmin) {
{ text: "Cancel", style: "cancel" }, visibleTabs = pendingTabs;
{
text: "Leave",
style: "destructive",
onPress: async () => {
const s = getSession();
const endpoint = `${API_BASE_URL}/subgroup-memberships?Uid=${s.currentUid}&Gid=${s.currentGid}`;
const res = await API.get(endpoint);
if (res.isSuccess && res.result.length > 0) {
await API.delete(
`${API_BASE_URL}/subgroup-memberships/${res.result[0].id}`,
);
} }
clearGameSession(); if (isGlobalMode) {
navigation.reset({ index: 0, routes: [{ name: "Game" }] }); visibleTabs = globalTabs;
},
},
]);
};
const handleLeaveGame = () => {
Alert.alert('Leave Game', 'Are you sure you want to leave this game?', [
{text: 'Cancel', style: 'cancel'},
{
text: 'Leave',
style: 'destructive',
onPress: async () => {
const s = getSession();
const endpoint = `${API_BASE_URL}/subgroup-members?Uid=${s.currentUid}&Gid=${s.currentGid}`;
const res = await API.get(endpoint);
if (res.isSuccess && res.result.length > 0) {
await API.delete(`${API_BASE_URL}/subgroup-members/${res.result[0].id}`);
} }
clearGameSession();
navigation.reset({index: 0, routes: [{name: 'Game'}]});
},
},
]);
};
// View ----------------------- // View -----------------------
return (
<View style={[styles.container, { paddingBottom: insets.bottom }]}>
{tabs.map((tab) => {
const isActive = routeName === tab.route;
const isLeave = tab.route === "__leave__";
return (
<Pressable
key={tab.label}
onPress={tab.onPress}
style={[
styles.tab,
isActive && styles.tabActive,
isLeave && styles.tabLeave,
]}
>
<Text
style={[
styles.tabText,
isActive && styles.tabTextActive,
isLeave && styles.tabTextLeave,
]}
>
{tab.label}
</Text>
</Pressable>
);
})}
</View>
);
};
const styles = StyleSheet.create({
container: {
minHeight: 64,
borderTopWidth: 1,
borderTopColor: "#555555",
backgroundColor: "#4a4a4a",
flexDirection: "row",
alignItems: "center",
justifyContent: "space-around",
paddingHorizontal: 8,
},
tab: {
flex: 1,
alignItems: "center",
justifyContent: "center",
paddingVertical: 12,
borderRadius: 8,
marginHorizontal: 4,
},
tabActive: {
backgroundColor: "#5c5c5c",
},
tabLeave: {
backgroundColor: "rgba(220,38,38,0.15)",
},
tabText: {
color: "#d1d5db",
fontSize: 12,
fontWeight: "600",
textAlign: "center",
},
tabTextActive: {
color: "#ffffff",
},
tabTextLeave: {
color: "#fca5a5",
},
return ( return (
<View style={[styles.container, {paddingBottom: insets.bottom}]}> <View style={[styles.container, {paddingBottom: insets.bottom}]}>
{visibleTabs.map((tab) => { {visibleTabs.map((tab) => {
const isActive = routeName === tab.route; const isActive = routeName === tab.route;
const isLeave = tab.route === '__leave__'; const isLeave = tab.route === '__leave__';
const isExitGlobal = tab.route === '__exit_global__';
const iconSource = iconMap[tab.label];
return ( return (
<Pressable <Pressable
key={tab.label} key={tab.label}
onPress={tab.onPress} onPress={tab.onPress}
style={[styles.tab, isActive && styles.tabActive, isLeave && styles.tabLeave]} style={[styles.tab, isActive && styles.tabActive, (isLeave || isExitGlobal) && styles.tabLeave]}
> >
<Text style={[styles.tabText, isActive && styles.tabTextActive, isLeave && styles.tabTextLeave]}> {iconSource ? <Image source={iconSource} style={styles.icon} resizeMode="contain" /> : null}
<Text style={[styles.tabText, isActive && styles.tabTextActive, (isLeave || isExitGlobal) && styles.tabTextLeave]}>
{tab.label} {tab.label}
</Text> </Text>
</Pressable> </Pressable>
@@ -269,14 +150,20 @@ const styles = StyleSheet.create({
flex: 1, flex: 1,
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
paddingVertical: 12, paddingVertical: 8,
marginHorizontal: 4, marginHorizontal: 4,
gap: 4,
}, },
tabActive: { tabActive: {
backgroundColor: '#45475a', backgroundColor: '#45475a',
}, },
tabLeave: { tabLeave: {
backgroundColor: 'rgba(243, 139, 168, 0.15)', backgroundColor: 'rgba(217, 40, 0, 0.15)',
},
icon: {
width: 16,
height: 16,
opacity: 0.95,
}, },
tabText: { tabText: {
color: '#bac2de', color: '#bac2de',
@@ -288,7 +175,7 @@ const styles = StyleSheet.create({
color: '#cdd6f4', color: '#cdd6f4',
}, },
tabTextLeave: { tabTextLeave: {
color: '#D92800', color: '#ffffff',
}, },
}); });
+25 -23
View File
@@ -1,9 +1,9 @@
import { StatusBar } from "expo-status-bar"; import {StatusBar} from 'expo-status-bar';
import { StyleSheet, View } from "react-native"; import {StyleSheet, View} from 'react-native';
import { useNavigation, useRoute } from "@react-navigation/native"; import {useNavigation, useRoute} from '@react-navigation/native';
import { useSafeAreaInsets } from "react-native-safe-area-context"; import {useSafeAreaInsets} from 'react-native-safe-area-context';
import TopNavbar from "./TopNavbar"; import TopNavbar from './TopNavbar';
import BottomNavbar from "./BottomNavbar"; import BottomNavbar from './BottomNavbar';
const Screen = ({children, style, showBack = false}) => { const Screen = ({children, style, showBack = false}) => {
// Initialisation ------------- // Initialisation -------------
@@ -13,26 +13,25 @@ const Screen = ({ children, style, showBack = false }) => {
const insets = useSafeAreaInsets(); const insets = useSafeAreaInsets();
const titleMap = { const titleMap = {
LeaderboardScreen: "Leaderboard", LeaderboardScreen: 'Leaderboard',
TeamScreen: "Current Team", TeamScreen: 'Current Team',
MapScreen: "Map", MapScreen: 'Map',
GameSettingsScreen: "Game Settings", GameSettingsScreen: 'Game Settings',
GameSelectionScreen: "Games", GameSelectionScreen: 'Games',
DepartmentSettingsScreen: "Department Settings", DepartmentSettingsScreen: 'Department Settings',
PlayersScreen: "Players", PlayersScreen: 'Players',
ExpandedMapScreen: "Map View", ExpandedMapScreen: 'Map View',
GlobalEventsScreen: "Global Events", GlobalEventsScreen: 'Global Events',
GlobalMapScreen: "Global Map", GlobalMapScreen: 'Global Map',
GlobalCacheViewScreen: "Cache Details", GlobalCacheViewScreen: 'Cache Details',
GlobalLeaderboardScreen: "Global Leaderboard", GlobalLeaderboardScreen: 'Global Leaderboard',
}; };
// State ---------------------- // State ----------------------
const routeName = route.name; const routeName = route.name;
const isAuthScreen = const isAuthScreen = routeName === 'LoginScreen' || routeName === 'RegisterScreen';
routeName === "LoginScreen" || routeName === "RegisterScreen"; const pageTitle = titleMap[routeName] || 'Treasure Hunt';
const pageTitle = titleMap[routeName] || "Treasure Hunt";
// Handlers ------------------- // Handlers -------------------
// View ----------------------- // View -----------------------
@@ -56,9 +55,12 @@ const Screen = ({ children, style, showBack = false }) => {
)} )}
<View style={[styles.content, style]}>{children}</View> <View style={[styles.content, style]}>{children}</View>
{!isAuthScreen && ( {!isAuthScreen && (
<BottomNavbar navigation={navigation} routeName={routeName} /> <BottomNavbar
navigation={navigation}
routeName={routeName}
routeParams={route.params}
/>
)} )}
{!isAuthScreen && <BottomNavbar navigation={navigation} routeName={routeName} routeParams={route.params} />}
<StatusBar style="light" /> <StatusBar style="light" />
</View> </View>
); );
+2 -1
View File
@@ -16,7 +16,8 @@ const TopNavbar = ({title, showBack, navigation}) => {
const handleLogout = () => { const handleLogout = () => {
clearSession(); clearSession();
navigation.reset({ const rootNav = navigation.getParent() || navigation;
rootNav.reset({
index: 0, index: 0,
routes: [{name: 'Auth', params: {screen: 'LoginScreen'}}], routes: [{name: 'Auth', params: {screen: 'LoginScreen'}}],
}); });
+1 -22
View File
@@ -34,10 +34,6 @@ const GameSelectionScreen = ({ navigation }) => {
navigation.navigate("TeamScreen"); navigation.navigate("TeamScreen");
}; };
const handleExplore = () => {
navigation.navigate("GlobalEventsScreen");
};
// View -------------------------------- // View --------------------------------
return ( return (
@@ -77,16 +73,8 @@ const GameSelectionScreen = ({ navigation }) => {
<View style={styles.sectionBlock}> <View style={styles.sectionBlock}>
<Text style={styles.blockTitle}>Global Mode</Text> <Text style={styles.blockTitle}>Global Mode</Text>
<Text style={styles.supportText}> <Text style={styles.supportText}>
Explore all visible caches and live activity from the map. Individual users can enter GLOBAL in the game join field to switch to Global mode.
</Text> </Text>
<ButtonTray>
<Button
label="Explore"
onClick={handleExplore}
styleButton={styles.neutralButton}
styleLabel={styles.neutralButtonLabel}
/>
</ButtonTray>
</View> </View>
</ScrollView> </ScrollView>
</Screen> </Screen>
@@ -170,15 +158,6 @@ const styles = StyleSheet.create({
fontWeight: "600", fontWeight: "600",
fontSize: 14, fontSize: 14,
}, },
neutralButton: {
backgroundColor: "#374151",
borderColor: "#374151",
maxWidth: 180,
},
neutralButtonLabel: {
color: "#ffffff",
fontWeight: "600",
},
}); });
export default GameSelectionScreen; export default GameSelectionScreen;
+4 -3
View File
@@ -15,11 +15,12 @@ const LoginScreen = ({ navigation }) => {
if (!user) { if (!user) {
return; return;
} }
const rootNav = navigation.getParent() || navigation;
if (user.IsAcceptedAdmin) { if (user.IsAcceptedAdmin) {
navigation.reset({ index: 0, routes: [{ name: 'Admin' }] }); rootNav.reset({ index: 0, routes: [{ name: 'Admin' }] });
return; return;
} }
navigation.reset({ index: 0, routes: [{ name: 'Game' }] }); rootNav.reset({ index: 0, routes: [{ name: 'Game' }] });
}; };
return ( return (
@@ -58,7 +59,7 @@ const LoginScreen = ({ navigation }) => {
/> />
</ButtonTray> </ButtonTray>
<Pressable onPress={() => navigation.navigate('Auth', { screen: 'RegisterScreen' })}> <Pressable onPress={() => navigation.navigate('RegisterScreen')}>
<Text style={styles.linkText}>Create new account</Text> <Text style={styles.linkText}>Create new account</Text>
</Pressable> </Pressable>
</View> </View>
+107 -63
View File
@@ -5,14 +5,25 @@ import * as Location from 'expo-location';
import {useFocusEffect} from '@react-navigation/native'; import {useFocusEffect} from '@react-navigation/native';
import Screen from '../layout/Screen'; import Screen from '../layout/Screen';
import {Button, ButtonTray} from '../UI/Button'; import {Button, ButtonTray} from '../UI/Button';
import CodeJoinRow from '../UI/CodeJoinRow';
import PendingApprovalView from '../UI/PendingApprovalView'; import PendingApprovalView from '../UI/PendingApprovalView';
import CacheCardItem from '../gameplay/CacheCardItem'; import CacheCardItem from '../gameplay/CacheCardItem';
import ClaimTimerView from '../gameplay/ClaimTimerView'; import ClaimTimerView from '../gameplay/ClaimTimerView';
import PlayerMapView from '../gameplay/PlayerMapView'; import PlayerMapView from '../gameplay/PlayerMapView';
import Card from '../UI/Card';
import useGameHook from '../../hooks/useGameHook'; import useGameHook from '../../hooks/useGameHook';
import usePlayerGame from '../../hooks/usePlayerGame'; import usePlayerGame from '../../hooks/usePlayerGame';
import {getSession, setPendingAdmin, setSelectedCache, setSessionGroup, setSessionTeam, setSessionUser} from '../../hooks/SessionStore'; import {
getSession,
setPendingAdmin,
setSelectedCache,
setSessionGroup,
setSessionMode,
setSessionTeam,
setSessionUser
} from '../../hooks/SessionStore';
import {getFovCone} from '../../utils/geoMath'; import {getFovCone} from '../../utils/geoMath';
import {GAME_MODE} from '../../utils/gameConstants';
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05}; const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05};
@@ -20,11 +31,17 @@ const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.
const MapScreen = ({navigation, route}) => { const MapScreen = ({navigation, route}) => {
const session = getSession(); const session = getSession();
const isBusiness = Boolean(session.isBusiness); const isBusiness = Boolean(session.isBusiness);
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, joinTeamByCode, createPrivateGame, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook(); const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, joinTeamByCode, createPrivateGame, getGroups, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook();
// Stable ref to prevent infinite re-render loops // Stable ref to prevent infinite re-render loops
const getCachesRef = useRef(getCaches); const getCachesRef = useRef(getCaches);
getCachesRef.current = getCaches; getCachesRef.current = getCaches;
const getLobbyRef = useRef(getLobby);
getLobbyRef.current = getLobby;
const getSubgroupsRef = useRef(getSubgroups);
getSubgroupsRef.current = getSubgroups;
const getGroupsRef = useRef(getGroups);
getGroupsRef.current = getGroups;
const lastAppliedRouteDepartmentRef = useRef(null); const lastAppliedRouteDepartmentRef = useRef(null);
const [inGame, setInGame] = useState(Boolean(session.currentGid)); const [inGame, setInGame] = useState(Boolean(session.currentGid));
@@ -39,6 +56,8 @@ const MapScreen = ({navigation, route}) => {
const [selectedCacheId, setSelectedCacheIdState] = useState(session.selectedCacheId); const [selectedCacheId, setSelectedCacheIdState] = useState(session.selectedCacheId);
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false); const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
const [subgroups, setSubgroups] = useState([]); const [subgroups, setSubgroups] = useState([]);
const [availableGames, setAvailableGames] = useState([]);
const [gamesLoading, setGamesLoading] = useState(false);
// Business join flow state // Business join flow state
const [joinStep, setJoinStep] = useState(null); const [joinStep, setJoinStep] = useState(null);
@@ -87,9 +106,26 @@ const MapScreen = ({navigation, route}) => {
useEffect(() => { useEffect(() => {
if (!session.currentGid) return; if (!session.currentGid) return;
getLobby(session.currentGid).then(setGroupInfo); getLobbyRef.current(session.currentGid).then(setGroupInfo);
getSubgroups(session.currentGid).then(setSubgroups); getSubgroupsRef.current(session.currentGid).then(setSubgroups);
}, [inGame]); }, [inGame, session.currentGid]);
useEffect(() => {
const loadAvailableGames = async () => {
if (inGame || session.isBusiness) {
setAvailableGames([]);
return;
}
setGamesLoading(true);
const groups = await getGroupsRef.current();
const individualGames = (groups || []).filter((group) => !group.BusinessOrSchoolName);
setAvailableGames(individualGames);
setGamesLoading(false);
};
loadAvailableGames();
}, [inGame, session.isBusiness]);
// Default member subgroup SGid for cache creation (first non-admin subgroup) // Default member subgroup SGid for cache creation (first non-admin subgroup)
const departments = useMemo( const departments = useMemo(
@@ -153,7 +189,7 @@ const MapScreen = ({navigation, route}) => {
const firstId = activeCachesForPlayer[0].id; const firstId = activeCachesForPlayer[0].id;
setSelectedCacheIdState(firstId); setSelectedCacheIdState(firstId);
setSelectedCache(firstId); setSelectedCache(firstId);
}, [activeCachesForPlayer, isPlayer]); }, [activeCachesForPlayer, isPlayer, selectedCacheId]);
// Location tracking // Location tracking
useEffect(() => { useEffect(() => {
@@ -198,9 +234,12 @@ const MapScreen = ({navigation, route}) => {
const handleJoinGame = async () => { const handleJoinGame = async () => {
if (!gameCode.trim()) return; if (!gameCode.trim()) return;
const result = await joinPrivateGame({JoinCode: gameCode.trim().toUpperCase(), Uid: session.currentUid}); const code = gameCode.trim().toUpperCase();
const result = await joinPrivateGame({JoinCode: code, Uid: session.currentUid});
if (!result) return; if (!result) return;
setSessionGroup(result.Gid, result.SGid); setSessionGroup(result.Gid, result.SGid);
setSessionMode(GAME_MODE.REGULAR);
setPendingAdmin(false); setPendingAdmin(false);
const freshUser = await getUser(session.currentUid); const freshUser = await getUser(session.currentUid);
@@ -214,6 +253,11 @@ const MapScreen = ({navigation, route}) => {
navigation.navigate('TeamScreen'); navigation.navigate('TeamScreen');
}; };
const handleEnterGlobal = () => {
setSessionMode(GAME_MODE.GLOBAL);
navigation.navigate('GlobalEventsScreen');
};
const handleCreateGame = async () => { const handleCreateGame = async () => {
const result = await createPrivateGame({ const result = await createPrivateGame({
GroupName: 'My Game', GroupName: 'My Game',
@@ -222,6 +266,7 @@ const MapScreen = ({navigation, route}) => {
MaxMemberSubgroups: 1, MaxMemberSubgroups: 1,
}); });
if (!result) return; if (!result) return;
setSessionMode(GAME_MODE.REGULAR);
setPendingAdmin(false); setPendingAdmin(false);
const freshUser = await getUser(session.currentUid); const freshUser = await getUser(session.currentUid);
if (freshUser) { if (freshUser) {
@@ -243,6 +288,7 @@ const MapScreen = ({navigation, route}) => {
isBusiness: true, isBusiness: true,
}); });
if (!result) return; if (!result) return;
setSessionMode(GAME_MODE.REGULAR);
setPendingAdmin(false); setPendingAdmin(false);
const freshUser = await getUser(session.currentUid); const freshUser = await getUser(session.currentUid);
if (freshUser) { if (freshUser) {
@@ -305,6 +351,7 @@ const MapScreen = ({navigation, route}) => {
return; return;
} }
setSessionGroup(result.Gid, result.SGid); setSessionGroup(result.Gid, result.SGid);
setSessionMode(GAME_MODE.REGULAR);
setPendingAdmin(false); setPendingAdmin(false);
const freshUser = await getUser(session.currentUid); const freshUser = await getUser(session.currentUid);
if (freshUser) { if (freshUser) {
@@ -332,7 +379,7 @@ const MapScreen = ({navigation, route}) => {
setTimeout(() => setClaimedPopupVisible(false), 3000); setTimeout(() => setClaimedPopupVisible(false), 3000);
} }
await loadCaches(); await loadCaches();
}, [session.currentGid, session.currentUid, session.currentTid, effectiveSGid, loadCaches]); }, [session.currentGid, session.currentUid, session.currentTid, effectiveSGid, claimCache, setIsClaiming, loadCaches]);
const handleCreateCachePress = () => { const handleCreateCachePress = () => {
const fallback = userLocation || {latitude: mapRegion.latitude, longitude: mapRegion.longitude}; const fallback = userLocation || {latitude: mapRegion.latitude, longitude: mapRegion.longitude};
@@ -421,22 +468,12 @@ const MapScreen = ({navigation, route}) => {
<Screen style={styles.center}> <Screen style={styles.center}>
<Text style={styles.bizTitle}>Join {matchedGroup.BusinessOrSchoolName || 'Company'}</Text> <Text style={styles.bizTitle}>Join {matchedGroup.BusinessOrSchoolName || 'Company'}</Text>
<Text style={styles.bizSubtitle}>Enter your department code to join</Text> <Text style={styles.bizSubtitle}>Enter your department code to join</Text>
<View style={styles.inputRow}> <CodeJoinRow
<TextInput
style={styles.codeInput}
placeholder="Department Code"
placeholderTextColor="#9ca3af"
value={deptCode} value={deptCode}
onChangeText={(text) => setDeptCode(text.toUpperCase())} onChange={setDeptCode}
autoCapitalize="characters" placeholder="Department Code"
onSubmit={handleJoinDepartment}
/> />
<Button
label="Join"
onClick={handleJoinDepartment}
styleButton={styles.joinButton}
styleLabel={styles.joinLabel}
/>
</View>
{joinError ? <Text style={styles.joinError}>{joinError}</Text> : null} {joinError ? <Text style={styles.joinError}>{joinError}</Text> : null}
<View style={styles.fullRow}> <View style={styles.fullRow}>
<ButtonTray> <ButtonTray>
@@ -457,22 +494,13 @@ const MapScreen = ({navigation, route}) => {
<Screen style={styles.center}> <Screen style={styles.center}>
<Text style={styles.bizTitle}>Welcome</Text> <Text style={styles.bizTitle}>Welcome</Text>
<Text style={styles.bizSubtitle}>Set up your organisation or join an existing one</Text> <Text style={styles.bizSubtitle}>Set up your organisation or join an existing one</Text>
<View style={styles.inputRow}> <CodeJoinRow
<TextInput
style={styles.codeInput}
placeholder="Company Code"
placeholderTextColor="#9ca3af"
value={orgCode} value={orgCode}
onChangeText={(text) => setOrgCode(text.toUpperCase())} onChange={setOrgCode}
autoCapitalize="characters" placeholder="Company Code"
buttonLabel="Join Company"
onSubmit={handleVerifyOrgCode}
/> />
<Button
label="Join Company"
onClick={handleVerifyOrgCode}
styleButton={styles.joinButton}
styleLabel={styles.joinLabel}
/>
</View>
{joinError ? <Text style={styles.joinError}>{joinError}</Text> : null} {joinError ? <Text style={styles.joinError}>{joinError}</Text> : null}
<View style={styles.fullRow}> <View style={styles.fullRow}>
<ButtonTray> <ButtonTray>
@@ -492,23 +520,23 @@ const MapScreen = ({navigation, route}) => {
if (!inGame) { if (!inGame) {
return ( return (
<Screen style={styles.center}> <Screen style={styles.center}>
<View style={styles.inputRow}> <View style={[styles.fullRow, styles.globalButtonRow]}>
<TextInput <ButtonTray>
style={styles.codeInput}
placeholder="Enter Game Code"
placeholderTextColor="#9ca3af"
value={gameCode}
onChangeText={(text) => setGameCode(text.toUpperCase())}
autoCapitalize="characters"
/>
<Button <Button
label="Join" label="Join Global Game"
onClick={handleJoinGame} onClick={handleEnterGlobal}
styleButton={styles.joinButton} styleButton={styles.globalJoinButton}
styleLabel={styles.joinLabel} styleLabel={styles.globalJoinLabel}
/> />
</ButtonTray>
</View> </View>
<View style={styles.fullRow}> <CodeJoinRow
value={gameCode}
onChange={setGameCode}
placeholder="Enter Game Code"
onSubmit={handleJoinGame}
/>
<View style={[styles.fullRow, styles.createGameRow]}>
<ButtonTray> <ButtonTray>
<Button <Button
label="Create a Game" label="Create a Game"
@@ -518,6 +546,23 @@ const MapScreen = ({navigation, route}) => {
/> />
</ButtonTray> </ButtonTray>
</View> </View>
<View style={[styles.fullRow, styles.availableGamesRow]}>
<Text style={styles.availableGamesTitle}>Available Individual Games</Text>
{gamesLoading ? (
<Text style={styles.availableGamesBody}>Loading games...</Text>
) : availableGames.length === 0 ? (
<Text style={styles.availableGamesBody}>No individual games available right now.</Text>
) : (
<ScrollView style={styles.availableGamesList} contentContainerStyle={styles.availableGamesContent}>
{availableGames.map((group) => (
<Card key={group.Gid} style={styles.availableGameCard}>
<Text style={styles.availableGameName}>{group.GroupName || `Game ${group.Gid}`}</Text>
<Text style={styles.availableGameMeta}>Game ID: {group.Gid}</Text>
</Card>
))}
</ScrollView>
)}
</View>
</Screen> </Screen>
); );
} }
@@ -794,20 +839,19 @@ const styles = StyleSheet.create({
mapContainer: {height: 200}, mapContainer: {height: 200},
error: {color: '#D92800', fontSize: 15}, error: {color: '#D92800', fontSize: 15},
loadingText: {color: '#6c7086', fontSize: 14, marginTop: 10}, loadingText: {color: '#6c7086', fontSize: 14, marginTop: 10},
inputRow: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
fullRow: {width: '100%', paddingHorizontal: 20}, fullRow: {width: '100%', paddingHorizontal: 20},
codeInput: { globalButtonRow: {marginBottom: 12},
flex: 1, createGameRow: {marginBottom: 12},
borderWidth: 1, availableGamesRow: {paddingTop: 4},
borderColor: '#45475a', globalJoinButton: {backgroundColor: '#bd93f9', borderColor: '#bd93f9'},
borderRadius: 8, globalJoinLabel: {color: '#1e1e2e', fontWeight: '600'},
paddingHorizontal: 12, availableGamesTitle: {color: '#cdd6f4', fontSize: 16, fontWeight: '700', marginBottom: 8},
paddingVertical: 10, availableGamesBody: {color: '#bac2de', fontSize: 14, marginBottom: 6},
fontSize: 16, availableGamesList: {maxHeight: 220},
color: '#cdd6f4', availableGamesContent: {paddingBottom: 10},
backgroundColor: '#313244', availableGameCard: {marginBottom: 8},
}, joinButton: {backgroundColor: '#bd93f9', borderColor: '#bd93f9', flex: 0, paddingHorizontal: 20}, availableGameName: {color: '#cdd6f4', fontSize: 15, fontWeight: '700', marginBottom: 3},
joinLabel: {color: '#1e1e2e', fontWeight: '600'}, availableGameMeta: {color: '#bac2de', fontSize: 12},
createButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'}, createButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
createLabel: {color: '#1e1e2e', fontWeight: '600'}, createLabel: {color: '#1e1e2e', fontWeight: '600'},
bizTitle: {fontSize: 22, fontWeight: '700', color: '#cdd6f4', marginBottom: 6, textAlign: 'center'}, bizTitle: {fontSize: 22, fontWeight: '700', color: '#cdd6f4', marginBottom: 6, textAlign: 'center'},
+4 -3
View File
@@ -19,11 +19,12 @@ const RegisterScreen = ({navigation}) => {
const user = await register(); const user = await register();
setSubmitting(false); setSubmitting(false);
if (!user) return; if (!user) return;
const rootNav = navigation.getParent() || navigation;
if (user.IsAcceptedAdmin) { if (user.IsAcceptedAdmin) {
navigation.reset({index: 0, routes: [{name: 'Admin'}]}); rootNav.reset({index: 0, routes: [{name: 'Admin'}]});
return; return;
} }
navigation.reset({index: 0, routes: [{name: 'Game'}]}); rootNav.reset({index: 0, routes: [{name: 'Game'}]});
}; };
return ( return (
@@ -100,7 +101,7 @@ const RegisterScreen = ({navigation}) => {
/> />
</ButtonTray> </ButtonTray>
<Pressable onPress={() => navigation.navigate('Auth', {screen: 'LoginScreen'})}> <Pressable onPress={() => navigation.navigate('LoginScreen')}>
<Text style={styles.linkText}>Back to Login</Text> <Text style={styles.linkText}>Back to Login</Text>
</Pressable> </Pressable>
</View> </View>
@@ -5,6 +5,7 @@ import { Button, ButtonTray } from "../../UI/Button";
import Card from "../../UI/Card"; import Card from "../../UI/Card";
import useGlobalHook from "../../../hooks/useGlobalHook"; import useGlobalHook from "../../../hooks/useGlobalHook";
import {getSession} from "../../../hooks/SessionStore"; import {getSession} from "../../../hooks/SessionStore";
import {GAME_MODE} from "../../../utils/gameConstants";
const GlobalCacheViewScreen = ({ navigation, route }) => { const GlobalCacheViewScreen = ({ navigation, route }) => {
// Initialisations --------------------- // Initialisations ---------------------
@@ -60,6 +61,12 @@ const GlobalCacheViewScreen = ({ navigation, route }) => {
); );
}; };
useEffect(() => {
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
navigation.replace("MapScreen");
}
}, [navigation, session.currentGameMode, session.isBusiness]);
// View -------------------------------- // View --------------------------------
return ( return (
@@ -1,16 +1,27 @@
import { useCallback, useEffect, useState } from "react"; import {useCallback, useEffect, useRef, useState} from "react";
import {ActivityIndicator, StyleSheet, Text, View} from "react-native"; import {ActivityIndicator, StyleSheet, Text, View} from "react-native";
import Screen from "../../layout/Screen"; import Screen from "../../layout/Screen";
import {Button, ButtonTray} from "../../UI/Button"; import {Button, ButtonTray} from "../../UI/Button";
import EventList from "../../../entity/event/EventList"; import EventList from "../../../entity/event/EventList";
import useGlobalHook from "../../../hooks/useGlobalHook"; import useGlobalHook from "../../../hooks/useGlobalHook";
import {getSession, setGlobalSession} from "../../../hooks/SessionStore"; import {getSession, setGlobalSession} from "../../../hooks/SessionStore";
import {GAME_MODE} from "../../../utils/gameConstants";
const GlobalEventsScreen = ({ navigation }) => { const GlobalEventsScreen = ({ navigation }) => {
// Initialisations --------------------- // Initialisations ---------------------
const { getPublicEvents, getPlayersByEvent, joinEvent } = useGlobalHook(); const { getPublicEvents, getPlayersByEvent, joinEvent } = useGlobalHook();
const session = getSession(); const session = getSession();
const globalApiRef = useRef({
getPublicEvents,
getPlayersByEvent,
joinEvent,
});
globalApiRef.current = {
getPublicEvents,
getPlayersByEvent,
joinEvent,
};
// State ------------------------------- // State -------------------------------
@@ -24,21 +35,25 @@ const GlobalEventsScreen = ({ navigation }) => {
const loadEvents = useCallback(async () => { const loadEvents = useCallback(async () => {
setIsLoading(true); setIsLoading(true);
setError(""); setError("");
const data = await getPublicEvents(); const data = await globalApiRef.current.getPublicEvents();
setEvents(data); setEvents(data);
setIsLoading(false); setIsLoading(false);
}, []); }, []);
useEffect(() => { useEffect(() => {
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
navigation.replace("MapScreen");
return;
}
loadEvents(); loadEvents();
}, [loadEvents]); }, [loadEvents, navigation, session.currentGameMode, session.isBusiness]);
const handleSelect = async (event) => { const handleSelect = async (event) => {
setError(""); setError("");
setJoining(event.EventID); setJoining(event.EventID);
// Check if already a player // Check if already a player
const players = await getPlayersByEvent(event.EventID); const players = await globalApiRef.current.getPlayersByEvent(event.EventID);
const existing = (players || []).find( const existing = (players || []).find(
(p) => p.PlayerUserID === session.currentUid, (p) => p.PlayerUserID === session.currentUid,
); );
@@ -48,7 +63,7 @@ const GlobalEventsScreen = ({ navigation }) => {
navigation.navigate("GlobalMapScreen", { eventId: event.EventID }); navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
} else { } else {
// Join the event // Join the event
const player = await joinEvent({ const player = await globalApiRef.current.joinEvent({
PlayerUserID: session.currentUid, PlayerUserID: session.currentUid,
PlayerEventID: event.EventID, PlayerEventID: event.EventID,
}); });
@@ -1,22 +1,17 @@
import {useCallback, useEffect, useState} from "react"; import {useCallback, useEffect, useState} from "react";
import { import {ActivityIndicator, ScrollView, StyleSheet, Text, View,} from "react-native";
ActivityIndicator,
ScrollView,
StyleSheet,
Text,
View,
} from "react-native";
import Screen from "../../layout/Screen"; import Screen from "../../layout/Screen";
import {Button, ButtonTray} from "../../UI/Button"; import {Button, ButtonTray} from "../../UI/Button";
import Card from "../../UI/Card"; import Card from "../../UI/Card";
import FindList from "../../../entity/find/FindList"; import FindList from "../../../entity/find/FindList";
import useGlobalHook from "../../../hooks/useGlobalHook"; import useGlobalHook from "../../../hooks/useGlobalHook";
import {getSession} from "../../../hooks/SessionStore"; import {getSession} from "../../../hooks/SessionStore";
import {GAME_MODE} from "../../../utils/gameConstants";
const GlobalLeaderboardScreen = ({ navigation, route }) => { const GlobalLeaderboardScreen = ({ navigation, route }) => {
// Initialisations --------------------- // Initialisations ---------------------
const { eventId } = route.params; const eventId = route?.params?.eventId ?? getSession().currentGlobalEventId;
const { getFindsByEvent, getCachesByEvent, getPlayersByEvent } = const { getFindsByEvent, getCachesByEvent, getPlayersByEvent } =
useGlobalHook(); useGlobalHook();
const session = getSession(); const session = getSession();
@@ -79,11 +74,19 @@ const GlobalLeaderboardScreen = ({ navigation, route }) => {
const ranked = Object.values(pointsMap).sort((a, b) => b.points - a.points); const ranked = Object.values(pointsMap).sort((a, b) => b.points - a.points);
setRankings(ranked); setRankings(ranked);
setIsLoading(false); setIsLoading(false);
}, [eventId]); }, [eventId, getFindsByEvent, getCachesByEvent, getPlayersByEvent]);
useEffect(() => { useEffect(() => {
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
navigation.replace("MapScreen");
return;
}
if (!eventId) {
navigation.replace("GlobalEventsScreen");
return;
}
loadData(); loadData();
}, [loadData]); }, [eventId, loadData, navigation, session.currentGameMode, session.isBusiness]);
// View -------------------------------- // View --------------------------------
@@ -7,6 +7,7 @@ import { Button, ButtonTray } from "../../UI/Button";
import CacheList from "../../../entity/cache/CacheList"; import CacheList from "../../../entity/cache/CacheList";
import useGlobalHook from "../../../hooks/useGlobalHook"; import useGlobalHook from "../../../hooks/useGlobalHook";
import {getSession} from "../../../hooks/SessionStore"; import {getSession} from "../../../hooks/SessionStore";
import {GAME_MODE} from "../../../utils/gameConstants";
const DEFAULT_REGION = { const DEFAULT_REGION = {
latitude: 51.5074, latitude: 51.5074,
@@ -18,7 +19,7 @@ const DEFAULT_REGION = {
const GlobalMapScreen = ({ navigation, route }) => { const GlobalMapScreen = ({ navigation, route }) => {
// Initialisations --------------------- // Initialisations ---------------------
const { eventId } = route.params; const eventId = route?.params?.eventId ?? getSession().currentGlobalEventId;
const { getCachesByEvent, getFindsByPlayer } = useGlobalHook(); const { getCachesByEvent, getFindsByPlayer } = useGlobalHook();
const session = getSession(); const session = getSession();
@@ -47,11 +48,19 @@ const GlobalMapScreen = ({ navigation, route }) => {
setCaches(cacheData); setCaches(cacheData);
setFoundCacheIds((findsData || []).map((f) => f.FindCacheID)); setFoundCacheIds((findsData || []).map((f) => f.FindCacheID));
setIsLoading(false); setIsLoading(false);
}, [eventId]); }, [eventId, getCachesByEvent, getFindsByPlayer, session.currentGlobalPlayerId]);
useEffect(() => { useEffect(() => {
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
navigation.replace("MapScreen");
return;
}
if (!eventId) {
navigation.replace("GlobalEventsScreen");
return;
}
loadData(); loadData();
}, [loadData]); }, [eventId, loadData, navigation, session.currentGameMode, session.isBusiness]);
useEffect(() => { useEffect(() => {
let sub; let sub;
+19 -3
View File
@@ -1,6 +1,5 @@
// --- Session Store (with AsyncStorage persistence) ---
import AsyncStorage from "@react-native-async-storage/async-storage"; import AsyncStorage from "@react-native-async-storage/async-storage";
import {GAME_MODE} from '../utils/gameConstants';
const STORAGE_KEY = "@mad_session"; const STORAGE_KEY = "@mad_session";
@@ -13,6 +12,7 @@ const session = {
isAcceptedAdmin: false, isAcceptedAdmin: false,
isPendingAdmin: false, isPendingAdmin: false,
teamsEnabled: false, teamsEnabled: false,
currentGameMode: null,
// Global gameplay // Global gameplay
currentGlobalEventId: null, currentGlobalEventId: null,
currentGlobalPlayerId: null, currentGlobalPlayerId: null,
@@ -31,11 +31,16 @@ const setSessionUser = (user) => {
session.isPendingAdmin = Boolean( session.isPendingAdmin = Boolean(
user?.isBusiness && user?.Gid && !user?.SGid && !user?.IsAcceptedAdmin, user?.isBusiness && user?.Gid && !user?.SGid && !user?.IsAcceptedAdmin,
); );
// New login/register starts in regular flow; global flow requires entering GLOBAL code.
session.currentGlobalEventId = null;
session.currentGlobalPlayerId = null;
session.currentGameMode = user?.Gid ? GAME_MODE.REGULAR : null;
persistSession(); persistSession();
}; };
const setSessionGroup = (gid, sgid = null) => { const setSessionGroup = (gid, sgid = null) => {
session.currentGid = gid ?? null; session.currentGid = gid ?? null;
session.currentGameMode = gid ? GAME_MODE.REGULAR : session.currentGameMode;
if (sgid !== undefined) { if (sgid !== undefined) {
session.currentSGid = sgid ?? null; session.currentSGid = sgid ?? null;
} }
@@ -67,12 +72,19 @@ const getSession = () => ({ ...session });
const setGlobalSession = (eventId, playerId) => { const setGlobalSession = (eventId, playerId) => {
session.currentGlobalEventId = eventId ?? null; session.currentGlobalEventId = eventId ?? null;
session.currentGlobalPlayerId = playerId ?? null; session.currentGlobalPlayerId = playerId ?? null;
session.currentGameMode = eventId ? GAME_MODE.GLOBAL : session.currentGameMode;
persistSession();
};
const setSessionMode = (mode) => {
session.currentGameMode = mode ?? null;
persistSession(); persistSession();
}; };
const clearGlobalSession = () => { const clearGlobalSession = () => {
session.currentGlobalEventId = null; session.currentGlobalEventId = null;
session.currentGlobalPlayerId = null; session.currentGlobalPlayerId = null;
if (!session.currentGid) session.currentGameMode = null;
persistSession(); persistSession();
}; };
@@ -83,6 +95,7 @@ const clearSession = () => {
session.currentTid = null; session.currentTid = null;
session.isBusiness = null; session.isBusiness = null;
session.isAcceptedAdmin = false; session.isAcceptedAdmin = false;
session.currentGameMode = null;
session.currentGlobalEventId = null; session.currentGlobalEventId = null;
session.currentGlobalPlayerId = null; session.currentGlobalPlayerId = null;
session.isPendingAdmin = false; session.isPendingAdmin = false;
@@ -98,6 +111,7 @@ const clearGameSession = () => {
session.isPendingAdmin = false; session.isPendingAdmin = false;
session.teamsEnabled = false; session.teamsEnabled = false;
session.selectedCacheId = null; session.selectedCacheId = null;
if (!session.currentGlobalEventId) session.currentGameMode = null;
persistSession(); persistSession();
}; };
@@ -125,11 +139,13 @@ export {
setSessionGroup, setSessionGroup,
setSessionTeam, setSessionTeam,
setSessionTeamsEnabled, setSessionTeamsEnabled,
setPendingAdmin,
setSelectedCache,
getSession, getSession,
clearSession, clearSession,
clearGameSession, clearGameSession,
loadSession, loadSession,
setGlobalSession, setGlobalSession,
clearGlobalSession, clearGlobalSession,
setSessionMode,
}; };
export { setSessionUser, setSessionGroup, setSessionTeam, setSessionTeamsEnabled, setPendingAdmin, setSelectedCache, getSession, clearSession, clearGameSession, loadSession };
+6
View File
@@ -31,6 +31,11 @@ const useGameHook = () => {
return response.isSuccess ? response.result : null; return response.isSuccess ? response.result : null;
}; };
const getGroups = async () => {
const response = await API.get(groupsEndpoint);
return response.isSuccess ? response.result : [];
};
const getLobby = async (gid) => { const getLobby = async (gid) => {
const response = await API.get(`${groupsEndpoint}/${gid}`); const response = await API.get(`${groupsEndpoint}/${gid}`);
return response.isSuccess ? response.result : null; return response.isSuccess ? response.result : null;
@@ -250,6 +255,7 @@ const useGameHook = () => {
getUser, getUser,
updateUser, updateUser,
createPrivateGame, createPrivateGame,
getGroups,
getLobby, getLobby,
updateGroup, updateGroup,
getGroupByOrgCode, getGroupByOrgCode,
+7
View File
@@ -0,0 +1,7 @@
export const GLOBAL_GAME_CODE = 'GLOBAL';
export const GAME_MODE = {
REGULAR: 'regular',
GLOBAL: 'global',
};