mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
fixed issues with merge + Global logic
This commit is contained in:
1 parent
b9b11463cc
commit
20a2edbbda
18 files changed
+386
-346
No files matched your search
@@ -12,7 +12,6 @@ import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
|
||||
import DepartmentSettingsScreen from "./src/components/screens/DepartmentSettingsScreen";
|
||||
import PlayersScreen from "./src/components/screens/PlayersScreen";
|
||||
import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen";
|
||||
import GameSelectionScreen from "./src/components/screens/GameSelectionScreen";
|
||||
import GlobalEventsScreen from "./src/components/screens/global/GlobalEventsScreen";
|
||||
import GlobalMapScreen from "./src/components/screens/global/GlobalMapScreen";
|
||||
import GlobalCacheViewScreen from "./src/components/screens/global/GlobalCacheViewScreen";
|
||||
@@ -26,7 +25,7 @@ const AdminStackNav = createNativeStackNavigator();
|
||||
|
||||
const screenOptions = {
|
||||
headerShown: false,
|
||||
contentStyle: { backgroundColor: "#ffffff" },
|
||||
contentStyle: { backgroundColor: "#1e1e2e" },
|
||||
};
|
||||
|
||||
const AuthStack = () => (
|
||||
@@ -40,10 +39,6 @@ const AuthStack = () => (
|
||||
|
||||
const GameStack = () => (
|
||||
<GameStackNav.Navigator screenOptions={screenOptions}>
|
||||
<GameStackNav.Screen
|
||||
name="GameSelectionScreen"
|
||||
component={GameSelectionScreen}
|
||||
/>
|
||||
<GameStackNav.Screen name="MapScreen" component={MapScreen} />
|
||||
<GameStackNav.Screen name="TeamScreen" component={TeamScreen} />
|
||||
<GameStackNav.Screen
|
||||
@@ -73,14 +68,14 @@ const GameStack = () => (
|
||||
|
||||
const AdminStack = () => (
|
||||
<AdminStackNav.Navigator screenOptions={screenOptions}>
|
||||
<AdminStackNav.Screen
|
||||
name="GameSelectionScreen"
|
||||
component={GameSelectionScreen}
|
||||
/>
|
||||
<AdminStackNav.Screen
|
||||
name="GameSettingsScreen"
|
||||
component={GameSettingsScreen}
|
||||
/>
|
||||
<AdminStackNav.Screen
|
||||
name="DepartmentSettingsScreen"
|
||||
component={DepartmentSettingsScreen}
|
||||
/>
|
||||
<AdminStackNav.Screen name="MapScreen" component={MapScreen} />
|
||||
<AdminStackNav.Screen
|
||||
name="LeaderboardScreen"
|
||||
@@ -106,15 +101,6 @@ const AdminStack = () => (
|
||||
component={GlobalLeaderboardScreen}
|
||||
/>
|
||||
</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 = () => {
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -36,6 +36,7 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
flexDirection: "row",
|
||||
gap: 5,
|
||||
marginHorizontal: 4,
|
||||
},
|
||||
label: {
|
||||
fontSize: 16,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,84 +1,63 @@
|
||||
import { Alert, Pressable, StyleSheet, Text, View } from "react-native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import { clearGameSession, getSession } from "../../hooks/SessionStore";
|
||||
import API, { API_BASE_URL } from "../API/API";
|
||||
import {Alert, Image, Pressable, StyleSheet, Text, View} from 'react-native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import {clearGameSession, clearGlobalSession, getSession, setSessionMode} from '../../hooks/SessionStore';
|
||||
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}) => {
|
||||
// Initialisation ------------
|
||||
|
||||
const insets = useSafeAreaInsets();
|
||||
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
|
||||
? {SGid: routeParams.SGid, SubGroupName: routeParams.SubGroupName || routeParams.selectedDepartmentName || 'Department'}
|
||||
? {
|
||||
SGid: routeParams.SGid,
|
||||
SubGroupName: routeParams.SubGroupName || routeParams.selectedDepartmentName || 'Department',
|
||||
}
|
||||
: 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 = [
|
||||
{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 = [
|
||||
{label: 'Team', route: 'TeamScreen', onPress: () => navigation.navigate('TeamScreen')},
|
||||
{label: 'Map', route: 'MapScreen', onPress: () => navigation.navigate('MapScreen')},
|
||||
{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 = [
|
||||
{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 ----------------------
|
||||
// Handlers -------------------
|
||||
const globalTabs = [
|
||||
{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 = () => {
|
||||
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-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}`,
|
||||
);
|
||||
let visibleTabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
|
||||
if (session.isPendingAdmin && !session.isAcceptedAdmin) {
|
||||
visibleTabs = pendingTabs;
|
||||
}
|
||||
clearGameSession();
|
||||
navigation.reset({ index: 0, routes: [{ name: "Game" }] });
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
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}`);
|
||||
if (isGlobalMode) {
|
||||
visibleTabs = globalTabs;
|
||||
}
|
||||
clearGameSession();
|
||||
navigation.reset({index: 0, routes: [{name: 'Game'}]});
|
||||
},
|
||||
},
|
||||
]);
|
||||
};
|
||||
|
||||
// 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 (
|
||||
<View style={[styles.container, {paddingBottom: insets.bottom}]}>
|
||||
{visibleTabs.map((tab) => {
|
||||
const isActive = routeName === tab.route;
|
||||
const isLeave = tab.route === '__leave__';
|
||||
const isExitGlobal = tab.route === '__exit_global__';
|
||||
const iconSource = iconMap[tab.label];
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
key={tab.label}
|
||||
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}
|
||||
</Text>
|
||||
</Pressable>
|
||||
@@ -269,14 +150,20 @@ const styles = StyleSheet.create({
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 12,
|
||||
paddingVertical: 8,
|
||||
marginHorizontal: 4,
|
||||
gap: 4,
|
||||
},
|
||||
tabActive: {
|
||||
backgroundColor: '#45475a',
|
||||
},
|
||||
tabLeave: {
|
||||
backgroundColor: 'rgba(243, 139, 168, 0.15)',
|
||||
backgroundColor: 'rgba(217, 40, 0, 0.15)',
|
||||
},
|
||||
icon: {
|
||||
width: 16,
|
||||
height: 16,
|
||||
opacity: 0.95,
|
||||
},
|
||||
tabText: {
|
||||
color: '#bac2de',
|
||||
@@ -288,7 +175,7 @@ const styles = StyleSheet.create({
|
||||
color: '#cdd6f4',
|
||||
},
|
||||
tabTextLeave: {
|
||||
color: '#D92800',
|
||||
color: '#ffffff',
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { StatusBar } from "expo-status-bar";
|
||||
import { StyleSheet, View } from "react-native";
|
||||
import { useNavigation, useRoute } from "@react-navigation/native";
|
||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||
import TopNavbar from "./TopNavbar";
|
||||
import BottomNavbar from "./BottomNavbar";
|
||||
import {StatusBar} from 'expo-status-bar';
|
||||
import {StyleSheet, View} from 'react-native';
|
||||
import {useNavigation, useRoute} from '@react-navigation/native';
|
||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||
import TopNavbar from './TopNavbar';
|
||||
import BottomNavbar from './BottomNavbar';
|
||||
|
||||
const Screen = ({children, style, showBack = false}) => {
|
||||
// Initialisation -------------
|
||||
@@ -13,26 +13,25 @@ const Screen = ({ children, style, showBack = false }) => {
|
||||
const insets = useSafeAreaInsets();
|
||||
|
||||
const titleMap = {
|
||||
LeaderboardScreen: "Leaderboard",
|
||||
TeamScreen: "Current Team",
|
||||
MapScreen: "Map",
|
||||
GameSettingsScreen: "Game Settings",
|
||||
GameSelectionScreen: "Games",
|
||||
DepartmentSettingsScreen: "Department Settings",
|
||||
PlayersScreen: "Players",
|
||||
ExpandedMapScreen: "Map View",
|
||||
GlobalEventsScreen: "Global Events",
|
||||
GlobalMapScreen: "Global Map",
|
||||
GlobalCacheViewScreen: "Cache Details",
|
||||
GlobalLeaderboardScreen: "Global Leaderboard",
|
||||
LeaderboardScreen: 'Leaderboard',
|
||||
TeamScreen: 'Current Team',
|
||||
MapScreen: 'Map',
|
||||
GameSettingsScreen: 'Game Settings',
|
||||
GameSelectionScreen: 'Games',
|
||||
DepartmentSettingsScreen: 'Department Settings',
|
||||
PlayersScreen: 'Players',
|
||||
ExpandedMapScreen: 'Map View',
|
||||
GlobalEventsScreen: 'Global Events',
|
||||
GlobalMapScreen: 'Global Map',
|
||||
GlobalCacheViewScreen: 'Cache Details',
|
||||
GlobalLeaderboardScreen: 'Global Leaderboard',
|
||||
};
|
||||
|
||||
// State ----------------------
|
||||
|
||||
const routeName = route.name;
|
||||
const isAuthScreen =
|
||||
routeName === "LoginScreen" || routeName === "RegisterScreen";
|
||||
const pageTitle = titleMap[routeName] || "Treasure Hunt";
|
||||
const isAuthScreen = routeName === 'LoginScreen' || routeName === 'RegisterScreen';
|
||||
const pageTitle = titleMap[routeName] || 'Treasure Hunt';
|
||||
|
||||
// Handlers -------------------
|
||||
// View -----------------------
|
||||
@@ -56,9 +55,12 @@ const Screen = ({ children, style, showBack = false }) => {
|
||||
)}
|
||||
<View style={[styles.content, style]}>{children}</View>
|
||||
{!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" />
|
||||
</View>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,8 @@ const TopNavbar = ({title, showBack, navigation}) => {
|
||||
|
||||
const handleLogout = () => {
|
||||
clearSession();
|
||||
navigation.reset({
|
||||
const rootNav = navigation.getParent() || navigation;
|
||||
rootNav.reset({
|
||||
index: 0,
|
||||
routes: [{name: 'Auth', params: {screen: 'LoginScreen'}}],
|
||||
});
|
||||
|
||||
@@ -34,10 +34,6 @@ const GameSelectionScreen = ({ navigation }) => {
|
||||
navigation.navigate("TeamScreen");
|
||||
};
|
||||
|
||||
const handleExplore = () => {
|
||||
navigation.navigate("GlobalEventsScreen");
|
||||
};
|
||||
|
||||
// View --------------------------------
|
||||
|
||||
return (
|
||||
@@ -77,16 +73,8 @@ const GameSelectionScreen = ({ navigation }) => {
|
||||
<View style={styles.sectionBlock}>
|
||||
<Text style={styles.blockTitle}>Global Mode</Text>
|
||||
<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>
|
||||
<ButtonTray>
|
||||
<Button
|
||||
label="Explore"
|
||||
onClick={handleExplore}
|
||||
styleButton={styles.neutralButton}
|
||||
styleLabel={styles.neutralButtonLabel}
|
||||
/>
|
||||
</ButtonTray>
|
||||
</View>
|
||||
</ScrollView>
|
||||
</Screen>
|
||||
@@ -170,15 +158,6 @@ const styles = StyleSheet.create({
|
||||
fontWeight: "600",
|
||||
fontSize: 14,
|
||||
},
|
||||
neutralButton: {
|
||||
backgroundColor: "#374151",
|
||||
borderColor: "#374151",
|
||||
maxWidth: 180,
|
||||
},
|
||||
neutralButtonLabel: {
|
||||
color: "#ffffff",
|
||||
fontWeight: "600",
|
||||
},
|
||||
});
|
||||
|
||||
export default GameSelectionScreen;
|
||||
@@ -15,11 +15,12 @@ const LoginScreen = ({ navigation }) => {
|
||||
if (!user) {
|
||||
return;
|
||||
}
|
||||
const rootNav = navigation.getParent() || navigation;
|
||||
if (user.IsAcceptedAdmin) {
|
||||
navigation.reset({ index: 0, routes: [{ name: 'Admin' }] });
|
||||
rootNav.reset({ index: 0, routes: [{ name: 'Admin' }] });
|
||||
return;
|
||||
}
|
||||
navigation.reset({ index: 0, routes: [{ name: 'Game' }] });
|
||||
rootNav.reset({ index: 0, routes: [{ name: 'Game' }] });
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -58,7 +59,7 @@ const LoginScreen = ({ navigation }) => {
|
||||
/>
|
||||
</ButtonTray>
|
||||
|
||||
<Pressable onPress={() => navigation.navigate('Auth', { screen: 'RegisterScreen' })}>
|
||||
<Pressable onPress={() => navigation.navigate('RegisterScreen')}>
|
||||
<Text style={styles.linkText}>Create new account</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -5,14 +5,25 @@ import * as Location from 'expo-location';
|
||||
import {useFocusEffect} from '@react-navigation/native';
|
||||
import Screen from '../layout/Screen';
|
||||
import {Button, ButtonTray} from '../UI/Button';
|
||||
import CodeJoinRow from '../UI/CodeJoinRow';
|
||||
import PendingApprovalView from '../UI/PendingApprovalView';
|
||||
import CacheCardItem from '../gameplay/CacheCardItem';
|
||||
import ClaimTimerView from '../gameplay/ClaimTimerView';
|
||||
import PlayerMapView from '../gameplay/PlayerMapView';
|
||||
import Card from '../UI/Card';
|
||||
import useGameHook from '../../hooks/useGameHook';
|
||||
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 {GAME_MODE} from '../../utils/gameConstants';
|
||||
|
||||
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 session = getSession();
|
||||
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
|
||||
const getCachesRef = useRef(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 [inGame, setInGame] = useState(Boolean(session.currentGid));
|
||||
@@ -39,6 +56,8 @@ const MapScreen = ({navigation, route}) => {
|
||||
const [selectedCacheId, setSelectedCacheIdState] = useState(session.selectedCacheId);
|
||||
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
|
||||
const [subgroups, setSubgroups] = useState([]);
|
||||
const [availableGames, setAvailableGames] = useState([]);
|
||||
const [gamesLoading, setGamesLoading] = useState(false);
|
||||
|
||||
// Business join flow state
|
||||
const [joinStep, setJoinStep] = useState(null);
|
||||
@@ -87,9 +106,26 @@ const MapScreen = ({navigation, route}) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (!session.currentGid) return;
|
||||
getLobby(session.currentGid).then(setGroupInfo);
|
||||
getSubgroups(session.currentGid).then(setSubgroups);
|
||||
}, [inGame]);
|
||||
getLobbyRef.current(session.currentGid).then(setGroupInfo);
|
||||
getSubgroupsRef.current(session.currentGid).then(setSubgroups);
|
||||
}, [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)
|
||||
const departments = useMemo(
|
||||
@@ -153,7 +189,7 @@ const MapScreen = ({navigation, route}) => {
|
||||
const firstId = activeCachesForPlayer[0].id;
|
||||
setSelectedCacheIdState(firstId);
|
||||
setSelectedCache(firstId);
|
||||
}, [activeCachesForPlayer, isPlayer]);
|
||||
}, [activeCachesForPlayer, isPlayer, selectedCacheId]);
|
||||
|
||||
// Location tracking
|
||||
useEffect(() => {
|
||||
@@ -198,9 +234,12 @@ const MapScreen = ({navigation, route}) => {
|
||||
|
||||
const handleJoinGame = async () => {
|
||||
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;
|
||||
setSessionGroup(result.Gid, result.SGid);
|
||||
setSessionMode(GAME_MODE.REGULAR);
|
||||
setPendingAdmin(false);
|
||||
|
||||
const freshUser = await getUser(session.currentUid);
|
||||
@@ -214,6 +253,11 @@ const MapScreen = ({navigation, route}) => {
|
||||
navigation.navigate('TeamScreen');
|
||||
};
|
||||
|
||||
const handleEnterGlobal = () => {
|
||||
setSessionMode(GAME_MODE.GLOBAL);
|
||||
navigation.navigate('GlobalEventsScreen');
|
||||
};
|
||||
|
||||
const handleCreateGame = async () => {
|
||||
const result = await createPrivateGame({
|
||||
GroupName: 'My Game',
|
||||
@@ -222,6 +266,7 @@ const MapScreen = ({navigation, route}) => {
|
||||
MaxMemberSubgroups: 1,
|
||||
});
|
||||
if (!result) return;
|
||||
setSessionMode(GAME_MODE.REGULAR);
|
||||
setPendingAdmin(false);
|
||||
const freshUser = await getUser(session.currentUid);
|
||||
if (freshUser) {
|
||||
@@ -243,6 +288,7 @@ const MapScreen = ({navigation, route}) => {
|
||||
isBusiness: true,
|
||||
});
|
||||
if (!result) return;
|
||||
setSessionMode(GAME_MODE.REGULAR);
|
||||
setPendingAdmin(false);
|
||||
const freshUser = await getUser(session.currentUid);
|
||||
if (freshUser) {
|
||||
@@ -305,6 +351,7 @@ const MapScreen = ({navigation, route}) => {
|
||||
return;
|
||||
}
|
||||
setSessionGroup(result.Gid, result.SGid);
|
||||
setSessionMode(GAME_MODE.REGULAR);
|
||||
setPendingAdmin(false);
|
||||
const freshUser = await getUser(session.currentUid);
|
||||
if (freshUser) {
|
||||
@@ -332,7 +379,7 @@ const MapScreen = ({navigation, route}) => {
|
||||
setTimeout(() => setClaimedPopupVisible(false), 3000);
|
||||
}
|
||||
await loadCaches();
|
||||
}, [session.currentGid, session.currentUid, session.currentTid, effectiveSGid, loadCaches]);
|
||||
}, [session.currentGid, session.currentUid, session.currentTid, effectiveSGid, claimCache, setIsClaiming, loadCaches]);
|
||||
|
||||
const handleCreateCachePress = () => {
|
||||
const fallback = userLocation || {latitude: mapRegion.latitude, longitude: mapRegion.longitude};
|
||||
@@ -421,22 +468,12 @@ const MapScreen = ({navigation, route}) => {
|
||||
<Screen style={styles.center}>
|
||||
<Text style={styles.bizTitle}>Join {matchedGroup.BusinessOrSchoolName || 'Company'}</Text>
|
||||
<Text style={styles.bizSubtitle}>Enter your department code to join</Text>
|
||||
<View style={styles.inputRow}>
|
||||
<TextInput
|
||||
style={styles.codeInput}
|
||||
placeholder="Department Code"
|
||||
placeholderTextColor="#9ca3af"
|
||||
<CodeJoinRow
|
||||
value={deptCode}
|
||||
onChangeText={(text) => setDeptCode(text.toUpperCase())}
|
||||
autoCapitalize="characters"
|
||||
onChange={setDeptCode}
|
||||
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}
|
||||
<View style={styles.fullRow}>
|
||||
<ButtonTray>
|
||||
@@ -457,22 +494,13 @@ const MapScreen = ({navigation, route}) => {
|
||||
<Screen style={styles.center}>
|
||||
<Text style={styles.bizTitle}>Welcome</Text>
|
||||
<Text style={styles.bizSubtitle}>Set up your organisation or join an existing one</Text>
|
||||
<View style={styles.inputRow}>
|
||||
<TextInput
|
||||
style={styles.codeInput}
|
||||
placeholder="Company Code"
|
||||
placeholderTextColor="#9ca3af"
|
||||
<CodeJoinRow
|
||||
value={orgCode}
|
||||
onChangeText={(text) => setOrgCode(text.toUpperCase())}
|
||||
autoCapitalize="characters"
|
||||
onChange={setOrgCode}
|
||||
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}
|
||||
<View style={styles.fullRow}>
|
||||
<ButtonTray>
|
||||
@@ -492,23 +520,23 @@ const MapScreen = ({navigation, route}) => {
|
||||
if (!inGame) {
|
||||
return (
|
||||
<Screen style={styles.center}>
|
||||
<View style={styles.inputRow}>
|
||||
<TextInput
|
||||
style={styles.codeInput}
|
||||
placeholder="Enter Game Code"
|
||||
placeholderTextColor="#9ca3af"
|
||||
value={gameCode}
|
||||
onChangeText={(text) => setGameCode(text.toUpperCase())}
|
||||
autoCapitalize="characters"
|
||||
/>
|
||||
<View style={[styles.fullRow, styles.globalButtonRow]}>
|
||||
<ButtonTray>
|
||||
<Button
|
||||
label="Join"
|
||||
onClick={handleJoinGame}
|
||||
styleButton={styles.joinButton}
|
||||
styleLabel={styles.joinLabel}
|
||||
label="Join Global Game"
|
||||
onClick={handleEnterGlobal}
|
||||
styleButton={styles.globalJoinButton}
|
||||
styleLabel={styles.globalJoinLabel}
|
||||
/>
|
||||
</ButtonTray>
|
||||
</View>
|
||||
<View style={styles.fullRow}>
|
||||
<CodeJoinRow
|
||||
value={gameCode}
|
||||
onChange={setGameCode}
|
||||
placeholder="Enter Game Code"
|
||||
onSubmit={handleJoinGame}
|
||||
/>
|
||||
<View style={[styles.fullRow, styles.createGameRow]}>
|
||||
<ButtonTray>
|
||||
<Button
|
||||
label="Create a Game"
|
||||
@@ -518,6 +546,23 @@ const MapScreen = ({navigation, route}) => {
|
||||
/>
|
||||
</ButtonTray>
|
||||
</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>
|
||||
);
|
||||
}
|
||||
@@ -794,20 +839,19 @@ const styles = StyleSheet.create({
|
||||
mapContainer: {height: 200},
|
||||
error: {color: '#D92800', fontSize: 15},
|
||||
loadingText: {color: '#6c7086', fontSize: 14, marginTop: 10},
|
||||
inputRow: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
||||
fullRow: {width: '100%', paddingHorizontal: 20},
|
||||
codeInput: {
|
||||
flex: 1,
|
||||
borderWidth: 1,
|
||||
borderColor: '#45475a',
|
||||
borderRadius: 8,
|
||||
paddingHorizontal: 12,
|
||||
paddingVertical: 10,
|
||||
fontSize: 16,
|
||||
color: '#cdd6f4',
|
||||
backgroundColor: '#313244',
|
||||
}, joinButton: {backgroundColor: '#bd93f9', borderColor: '#bd93f9', flex: 0, paddingHorizontal: 20},
|
||||
joinLabel: {color: '#1e1e2e', fontWeight: '600'},
|
||||
globalButtonRow: {marginBottom: 12},
|
||||
createGameRow: {marginBottom: 12},
|
||||
availableGamesRow: {paddingTop: 4},
|
||||
globalJoinButton: {backgroundColor: '#bd93f9', borderColor: '#bd93f9'},
|
||||
globalJoinLabel: {color: '#1e1e2e', fontWeight: '600'},
|
||||
availableGamesTitle: {color: '#cdd6f4', fontSize: 16, fontWeight: '700', marginBottom: 8},
|
||||
availableGamesBody: {color: '#bac2de', fontSize: 14, marginBottom: 6},
|
||||
availableGamesList: {maxHeight: 220},
|
||||
availableGamesContent: {paddingBottom: 10},
|
||||
availableGameCard: {marginBottom: 8},
|
||||
availableGameName: {color: '#cdd6f4', fontSize: 15, fontWeight: '700', marginBottom: 3},
|
||||
availableGameMeta: {color: '#bac2de', fontSize: 12},
|
||||
createButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
|
||||
createLabel: {color: '#1e1e2e', fontWeight: '600'},
|
||||
bizTitle: {fontSize: 22, fontWeight: '700', color: '#cdd6f4', marginBottom: 6, textAlign: 'center'},
|
||||
|
||||
@@ -19,11 +19,12 @@ const RegisterScreen = ({navigation}) => {
|
||||
const user = await register();
|
||||
setSubmitting(false);
|
||||
if (!user) return;
|
||||
const rootNav = navigation.getParent() || navigation;
|
||||
if (user.IsAcceptedAdmin) {
|
||||
navigation.reset({index: 0, routes: [{name: 'Admin'}]});
|
||||
rootNav.reset({index: 0, routes: [{name: 'Admin'}]});
|
||||
return;
|
||||
}
|
||||
navigation.reset({index: 0, routes: [{name: 'Game'}]});
|
||||
rootNav.reset({index: 0, routes: [{name: 'Game'}]});
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -100,7 +101,7 @@ const RegisterScreen = ({navigation}) => {
|
||||
/>
|
||||
</ButtonTray>
|
||||
|
||||
<Pressable onPress={() => navigation.navigate('Auth', {screen: 'LoginScreen'})}>
|
||||
<Pressable onPress={() => navigation.navigate('LoginScreen')}>
|
||||
<Text style={styles.linkText}>Back to Login</Text>
|
||||
</Pressable>
|
||||
</View>
|
||||
|
||||
@@ -5,6 +5,7 @@ import { Button, ButtonTray } from "../../UI/Button";
|
||||
import Card from "../../UI/Card";
|
||||
import useGlobalHook from "../../../hooks/useGlobalHook";
|
||||
import {getSession} from "../../../hooks/SessionStore";
|
||||
import {GAME_MODE} from "../../../utils/gameConstants";
|
||||
|
||||
const GlobalCacheViewScreen = ({ navigation, route }) => {
|
||||
// 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 --------------------------------
|
||||
|
||||
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 Screen from "../../layout/Screen";
|
||||
import {Button, ButtonTray} from "../../UI/Button";
|
||||
import EventList from "../../../entity/event/EventList";
|
||||
import useGlobalHook from "../../../hooks/useGlobalHook";
|
||||
import {getSession, setGlobalSession} from "../../../hooks/SessionStore";
|
||||
import {GAME_MODE} from "../../../utils/gameConstants";
|
||||
|
||||
const GlobalEventsScreen = ({ navigation }) => {
|
||||
// Initialisations ---------------------
|
||||
|
||||
const { getPublicEvents, getPlayersByEvent, joinEvent } = useGlobalHook();
|
||||
const session = getSession();
|
||||
const globalApiRef = useRef({
|
||||
getPublicEvents,
|
||||
getPlayersByEvent,
|
||||
joinEvent,
|
||||
});
|
||||
globalApiRef.current = {
|
||||
getPublicEvents,
|
||||
getPlayersByEvent,
|
||||
joinEvent,
|
||||
};
|
||||
|
||||
// State -------------------------------
|
||||
|
||||
@@ -24,21 +35,25 @@ const GlobalEventsScreen = ({ navigation }) => {
|
||||
const loadEvents = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
setError("");
|
||||
const data = await getPublicEvents();
|
||||
const data = await globalApiRef.current.getPublicEvents();
|
||||
setEvents(data);
|
||||
setIsLoading(false);
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
|
||||
navigation.replace("MapScreen");
|
||||
return;
|
||||
}
|
||||
loadEvents();
|
||||
}, [loadEvents]);
|
||||
}, [loadEvents, navigation, session.currentGameMode, session.isBusiness]);
|
||||
|
||||
const handleSelect = async (event) => {
|
||||
setError("");
|
||||
setJoining(event.EventID);
|
||||
|
||||
// Check if already a player
|
||||
const players = await getPlayersByEvent(event.EventID);
|
||||
const players = await globalApiRef.current.getPlayersByEvent(event.EventID);
|
||||
const existing = (players || []).find(
|
||||
(p) => p.PlayerUserID === session.currentUid,
|
||||
);
|
||||
@@ -48,7 +63,7 @@ const GlobalEventsScreen = ({ navigation }) => {
|
||||
navigation.navigate("GlobalMapScreen", { eventId: event.EventID });
|
||||
} else {
|
||||
// Join the event
|
||||
const player = await joinEvent({
|
||||
const player = await globalApiRef.current.joinEvent({
|
||||
PlayerUserID: session.currentUid,
|
||||
PlayerEventID: event.EventID,
|
||||
});
|
||||
|
||||
@@ -1,22 +1,17 @@
|
||||
import {useCallback, useEffect, useState} from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
ScrollView,
|
||||
StyleSheet,
|
||||
Text,
|
||||
View,
|
||||
} from "react-native";
|
||||
import {ActivityIndicator, ScrollView, StyleSheet, Text, View,} from "react-native";
|
||||
import Screen from "../../layout/Screen";
|
||||
import {Button, ButtonTray} from "../../UI/Button";
|
||||
import Card from "../../UI/Card";
|
||||
import FindList from "../../../entity/find/FindList";
|
||||
import useGlobalHook from "../../../hooks/useGlobalHook";
|
||||
import {getSession} from "../../../hooks/SessionStore";
|
||||
import {GAME_MODE} from "../../../utils/gameConstants";
|
||||
|
||||
const GlobalLeaderboardScreen = ({ navigation, route }) => {
|
||||
// Initialisations ---------------------
|
||||
|
||||
const { eventId } = route.params;
|
||||
const eventId = route?.params?.eventId ?? getSession().currentGlobalEventId;
|
||||
const { getFindsByEvent, getCachesByEvent, getPlayersByEvent } =
|
||||
useGlobalHook();
|
||||
const session = getSession();
|
||||
@@ -79,11 +74,19 @@ const GlobalLeaderboardScreen = ({ navigation, route }) => {
|
||||
const ranked = Object.values(pointsMap).sort((a, b) => b.points - a.points);
|
||||
setRankings(ranked);
|
||||
setIsLoading(false);
|
||||
}, [eventId]);
|
||||
}, [eventId, getFindsByEvent, getCachesByEvent, getPlayersByEvent]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
|
||||
navigation.replace("MapScreen");
|
||||
return;
|
||||
}
|
||||
if (!eventId) {
|
||||
navigation.replace("GlobalEventsScreen");
|
||||
return;
|
||||
}
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
}, [eventId, loadData, navigation, session.currentGameMode, session.isBusiness]);
|
||||
|
||||
// View --------------------------------
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Button, ButtonTray } from "../../UI/Button";
|
||||
import CacheList from "../../../entity/cache/CacheList";
|
||||
import useGlobalHook from "../../../hooks/useGlobalHook";
|
||||
import {getSession} from "../../../hooks/SessionStore";
|
||||
import {GAME_MODE} from "../../../utils/gameConstants";
|
||||
|
||||
const DEFAULT_REGION = {
|
||||
latitude: 51.5074,
|
||||
@@ -18,7 +19,7 @@ const DEFAULT_REGION = {
|
||||
const GlobalMapScreen = ({ navigation, route }) => {
|
||||
// Initialisations ---------------------
|
||||
|
||||
const { eventId } = route.params;
|
||||
const eventId = route?.params?.eventId ?? getSession().currentGlobalEventId;
|
||||
const { getCachesByEvent, getFindsByPlayer } = useGlobalHook();
|
||||
const session = getSession();
|
||||
|
||||
@@ -47,11 +48,19 @@ const GlobalMapScreen = ({ navigation, route }) => {
|
||||
setCaches(cacheData);
|
||||
setFoundCacheIds((findsData || []).map((f) => f.FindCacheID));
|
||||
setIsLoading(false);
|
||||
}, [eventId]);
|
||||
}, [eventId, getCachesByEvent, getFindsByPlayer, session.currentGlobalPlayerId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
|
||||
navigation.replace("MapScreen");
|
||||
return;
|
||||
}
|
||||
if (!eventId) {
|
||||
navigation.replace("GlobalEventsScreen");
|
||||
return;
|
||||
}
|
||||
loadData();
|
||||
}, [loadData]);
|
||||
}, [eventId, loadData, navigation, session.currentGameMode, session.isBusiness]);
|
||||
|
||||
useEffect(() => {
|
||||
let sub;
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// --- Session Store (with AsyncStorage persistence) ---
|
||||
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import {GAME_MODE} from '../utils/gameConstants';
|
||||
|
||||
const STORAGE_KEY = "@mad_session";
|
||||
|
||||
@@ -13,6 +12,7 @@ const session = {
|
||||
isAcceptedAdmin: false,
|
||||
isPendingAdmin: false,
|
||||
teamsEnabled: false,
|
||||
currentGameMode: null,
|
||||
// Global gameplay
|
||||
currentGlobalEventId: null,
|
||||
currentGlobalPlayerId: null,
|
||||
@@ -31,11 +31,16 @@ const setSessionUser = (user) => {
|
||||
session.isPendingAdmin = Boolean(
|
||||
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();
|
||||
};
|
||||
|
||||
const setSessionGroup = (gid, sgid = null) => {
|
||||
session.currentGid = gid ?? null;
|
||||
session.currentGameMode = gid ? GAME_MODE.REGULAR : session.currentGameMode;
|
||||
if (sgid !== undefined) {
|
||||
session.currentSGid = sgid ?? null;
|
||||
}
|
||||
@@ -67,12 +72,19 @@ const getSession = () => ({ ...session });
|
||||
const setGlobalSession = (eventId, playerId) => {
|
||||
session.currentGlobalEventId = eventId ?? null;
|
||||
session.currentGlobalPlayerId = playerId ?? null;
|
||||
session.currentGameMode = eventId ? GAME_MODE.GLOBAL : session.currentGameMode;
|
||||
persistSession();
|
||||
};
|
||||
|
||||
const setSessionMode = (mode) => {
|
||||
session.currentGameMode = mode ?? null;
|
||||
persistSession();
|
||||
};
|
||||
|
||||
const clearGlobalSession = () => {
|
||||
session.currentGlobalEventId = null;
|
||||
session.currentGlobalPlayerId = null;
|
||||
if (!session.currentGid) session.currentGameMode = null;
|
||||
persistSession();
|
||||
};
|
||||
|
||||
@@ -83,6 +95,7 @@ const clearSession = () => {
|
||||
session.currentTid = null;
|
||||
session.isBusiness = null;
|
||||
session.isAcceptedAdmin = false;
|
||||
session.currentGameMode = null;
|
||||
session.currentGlobalEventId = null;
|
||||
session.currentGlobalPlayerId = null;
|
||||
session.isPendingAdmin = false;
|
||||
@@ -98,6 +111,7 @@ const clearGameSession = () => {
|
||||
session.isPendingAdmin = false;
|
||||
session.teamsEnabled = false;
|
||||
session.selectedCacheId = null;
|
||||
if (!session.currentGlobalEventId) session.currentGameMode = null;
|
||||
persistSession();
|
||||
};
|
||||
|
||||
@@ -125,11 +139,13 @@ export {
|
||||
setSessionGroup,
|
||||
setSessionTeam,
|
||||
setSessionTeamsEnabled,
|
||||
setPendingAdmin,
|
||||
setSelectedCache,
|
||||
getSession,
|
||||
clearSession,
|
||||
clearGameSession,
|
||||
loadSession,
|
||||
setGlobalSession,
|
||||
clearGlobalSession,
|
||||
setSessionMode,
|
||||
};
|
||||
export { setSessionUser, setSessionGroup, setSessionTeam, setSessionTeamsEnabled, setPendingAdmin, setSelectedCache, getSession, clearSession, clearGameSession, loadSession };
|
||||
@@ -31,6 +31,11 @@ const useGameHook = () => {
|
||||
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 response = await API.get(`${groupsEndpoint}/${gid}`);
|
||||
return response.isSuccess ? response.result : null;
|
||||
@@ -250,6 +255,7 @@ const useGameHook = () => {
|
||||
getUser,
|
||||
updateUser,
|
||||
createPrivateGame,
|
||||
getGroups,
|
||||
getLobby,
|
||||
updateGroup,
|
||||
getGroupByOrgCode,
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
export const GLOBAL_GAME_CODE = 'GLOBAL';
|
||||
|
||||
export const GAME_MODE = {
|
||||
REGULAR: 'regular',
|
||||
GLOBAL: 'global',
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user