mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
Add GameSettings and Players screens; implement cache management features in MapScreen
This commit is contained in:
1 parent
b8efe987af
commit
8008a9b6fe
22 files changed
+907
-82
No files matched your search
@@ -14,6 +14,8 @@ import MapScreen from "./src/components/screens/MapScreen";
|
|||||||
import LeaderboardScreen from "./src/components/screens/LeaderboardScreen";
|
import LeaderboardScreen from "./src/components/screens/LeaderboardScreen";
|
||||||
import AdminHubScreen from "./src/components/screens/AdminHubScreen";
|
import AdminHubScreen from "./src/components/screens/AdminHubScreen";
|
||||||
import ManageSubgroupsScreen from "./src/components/screens/ManageSubgroupsScreen";
|
import ManageSubgroupsScreen from "./src/components/screens/ManageSubgroupsScreen";
|
||||||
|
import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
|
||||||
|
import PlayersScreen from "./src/components/screens/PlayersScreen";
|
||||||
|
|
||||||
const RootStack = createNativeStackNavigator();
|
const RootStack = createNativeStackNavigator();
|
||||||
const AuthStackNav = createNativeStackNavigator();
|
const AuthStackNav = createNativeStackNavigator();
|
||||||
@@ -43,6 +45,8 @@ const GameStack = () => (
|
|||||||
<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 name="LeaderboardScreen" component={LeaderboardScreen}/>
|
<GameStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
||||||
|
<GameStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
|
||||||
|
<GameStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
|
||||||
<GameStackNav.Screen name="GameTypeScreen" component={GameTypeScreen}/>
|
<GameStackNav.Screen name="GameTypeScreen" component={GameTypeScreen}/>
|
||||||
<GameStackNav.Screen name="JoinPrivateGameScreen" component={JoinPrivateGameScreen}/>
|
<GameStackNav.Screen name="JoinPrivateGameScreen" component={JoinPrivateGameScreen}/>
|
||||||
<GameStackNav.Screen name="ManagePrivateGameScreen" component={ManagePrivateGameScreen}/>
|
<GameStackNav.Screen name="ManagePrivateGameScreen" component={ManagePrivateGameScreen}/>
|
||||||
@@ -59,10 +63,12 @@ const AdminStack = () => (
|
|||||||
contentStyle: { backgroundColor: '#ffffff' },
|
contentStyle: { backgroundColor: '#ffffff' },
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<AdminStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
|
||||||
|
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
|
||||||
|
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
||||||
|
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
|
||||||
<AdminStackNav.Screen name="AdminHubScreen" component={AdminHubScreen}/>
|
<AdminStackNav.Screen name="AdminHubScreen" component={AdminHubScreen}/>
|
||||||
<AdminStackNav.Screen name="ManageSubgroupsScreen" component={ManageSubgroupsScreen}/>
|
<AdminStackNav.Screen name="ManageSubgroupsScreen" component={ManageSubgroupsScreen}/>
|
||||||
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
|
||||||
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
|
|
||||||
<AdminStackNav.Screen name="TeamScreen" component={TeamScreen}/>
|
<AdminStackNav.Screen name="TeamScreen" component={TeamScreen}/>
|
||||||
<AdminStackNav.Screen name="CreateGameScreen" component={CreateGameScreen}/>
|
<AdminStackNav.Screen name="CreateGameScreen" component={CreateGameScreen}/>
|
||||||
<AdminStackNav.Screen name="ManageGameScreen" component={ManageGameScreen}/>
|
<AdminStackNav.Screen name="ManageGameScreen" component={ManageGameScreen}/>
|
||||||
|
|||||||
@@ -13,14 +13,28 @@
|
|||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"ios": {
|
"ios": {
|
||||||
"supportsTablet": true
|
"supportsTablet": true,
|
||||||
|
"infoPlist": {
|
||||||
|
"NSLocationWhenInUseUsageDescription": "This app needs your location for the treasure hunt."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"android": {
|
"android": {
|
||||||
"adaptiveIcon": {
|
"adaptiveIcon": {
|
||||||
"foregroundImage": "./assets/images/android-icon-monochrome.png",
|
"foregroundImage": "./assets/images/android-icon-monochrome.png",
|
||||||
"backgroundColor": "#ffffff"
|
"backgroundColor": "#ffffff"
|
||||||
},
|
},
|
||||||
"edgeToEdgeEnabled": false
|
"config": {
|
||||||
|
"googleMaps": {
|
||||||
|
"apiKey": "AIzaSyA-G0tSzzcM5z6HyI23s8dzKDLCOrekhbw"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"ACCESS_FINE_LOCATION",
|
||||||
|
"ACCESS_COARSE_LOCATION",
|
||||||
|
"ACCESS_FINE_LOCATION",
|
||||||
|
"ACCESS_COARSE_LOCATION"
|
||||||
|
],
|
||||||
|
"package": "com.bobthebob.MAD_Treasure_Hunt_App"
|
||||||
},
|
},
|
||||||
"web": {
|
"web": {
|
||||||
"favicon": "./assets/favicon.png"
|
"favicon": "./assets/favicon.png"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export const API_BASE_URL = 'http://api.bobby.ip-ddns.com'; //For outside Home network
|
//export const API_BASE_URL = 'http://api.bobby.ip-ddns.com'; //For outside Home network
|
||||||
//export const API_BASE_URL = 'http://192.168.8.100:3000'; //For when im home (API Endpoint is on my home server...)
|
export const API_BASE_URL = 'http://192.168.8.100:3000'; //For when im home (API Endpoint is on my home server...)
|
||||||
const API = {};
|
const API = {};
|
||||||
|
|
||||||
API.get = (endpoint) => callFetch(endpoint, 'GET');
|
API.get = (endpoint) => callFetch(endpoint, 'GET');
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import {StyleSheet, View} from 'react-native';
|
||||||
|
|
||||||
|
const Card = ({children, style}) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.card, style]}>{children}</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
card: {
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
borderRadius: 8,
|
||||||
|
padding: 12,
|
||||||
|
marginBottom: 8,
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#e5e7eb',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default Card;
|
||||||
|
|
||||||
@@ -19,12 +19,9 @@ const AdminCacheEditorView = ({ userLocation, caches, onAddCache, onMoveCache })
|
|||||||
latitudeDelta: 0.01,
|
latitudeDelta: 0.01,
|
||||||
longitudeDelta: 0.01,
|
longitudeDelta: 0.01,
|
||||||
}}
|
}}
|
||||||
region={{
|
|
||||||
...userLocation,
|
|
||||||
latitudeDelta: 0.01,
|
|
||||||
longitudeDelta: 0.01,
|
|
||||||
}}
|
|
||||||
onLongPress={(event) => onAddCache(event.nativeEvent.coordinate)}
|
onLongPress={(event) => onAddCache(event.nativeEvent.coordinate)}
|
||||||
|
scrollEnabled={true}
|
||||||
|
zoomEnabled={true}
|
||||||
showsUserLocation
|
showsUserLocation
|
||||||
>
|
>
|
||||||
{(caches || []).map((cache) => (
|
{(caches || []).map((cache) => (
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import {StyleSheet, Text, View} from 'react-native';
|
||||||
|
import Card from '../UI/Card';
|
||||||
|
import {Button} from '../UI/Button';
|
||||||
|
|
||||||
|
const CacheCardItem = ({cache, isAdmin, onEdit, onDelete, onSelect}) => {
|
||||||
|
// Initialisation -------------
|
||||||
|
|
||||||
|
const isClaimed = Boolean(cache.ClaimedByUid);
|
||||||
|
const statusText = isClaimed ? 'Claimed' : 'Available';
|
||||||
|
const statusColor = isClaimed ? '#9ca3af' : '#16a34a';
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
// Handlers -------------------
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card>
|
||||||
|
<View style={styles.topRow}>
|
||||||
|
<Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text>
|
||||||
|
<Text style={[styles.status, {color: statusColor}]}>{statusText}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.actions}>
|
||||||
|
{isAdmin ? (
|
||||||
|
<>
|
||||||
|
<Button
|
||||||
|
label="Edit"
|
||||||
|
onClick={() => onEdit(cache)}
|
||||||
|
styleButton={styles.editBtn}
|
||||||
|
styleLabel={styles.btnLabel}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Delete"
|
||||||
|
onClick={() => onDelete(cache)}
|
||||||
|
styleButton={styles.deleteBtn}
|
||||||
|
styleLabel={styles.btnLabel}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<Button
|
||||||
|
label="Select Cache"
|
||||||
|
onClick={() => onSelect(cache)}
|
||||||
|
styleButton={styles.selectBtn}
|
||||||
|
styleLabel={styles.btnLabel}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
topRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginBottom: 8,
|
||||||
|
},
|
||||||
|
clue: {fontSize: 15, fontWeight: '600', color: '#1f2937', flex: 1, marginRight: 8},
|
||||||
|
status: {fontSize: 13, fontWeight: '600'},
|
||||||
|
actions: {flexDirection: 'row', gap: 8},
|
||||||
|
editBtn: {
|
||||||
|
backgroundColor: '#2563eb',
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
minHeight: 36,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
deleteBtn: {
|
||||||
|
backgroundColor: '#dc2626',
|
||||||
|
borderColor: '#dc2626',
|
||||||
|
minHeight: 36,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
selectBtn: {
|
||||||
|
backgroundColor: '#16a34a',
|
||||||
|
borderColor: '#16a34a',
|
||||||
|
minHeight: 36,
|
||||||
|
paddingHorizontal: 14,
|
||||||
|
flex: 1,
|
||||||
|
},
|
||||||
|
btnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default CacheCardItem;
|
||||||
|
|
||||||
@@ -13,17 +13,12 @@ const PlayerMapView = ({ userLocation, visibleCache, isClaiming, onClaimSuccess
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<MapView
|
<MapView
|
||||||
style={{ flex: 1 }}
|
style={{flex: 1}}
|
||||||
initialRegion={{
|
initialRegion={{
|
||||||
...userLocation,
|
...userLocation,
|
||||||
latitudeDelta: 0.01,
|
latitudeDelta: 0.01,
|
||||||
longitudeDelta: 0.01,
|
longitudeDelta: 0.01,
|
||||||
}}
|
}}
|
||||||
region={{
|
|
||||||
...userLocation,
|
|
||||||
latitudeDelta: 0.01,
|
|
||||||
longitudeDelta: 0.01,
|
|
||||||
}}
|
|
||||||
showsUserLocation
|
showsUserLocation
|
||||||
>
|
>
|
||||||
{visibleCache ? (
|
{visibleCache ? (
|
||||||
|
|||||||
@@ -1,29 +1,56 @@
|
|||||||
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
import {Pressable, StyleSheet, Text, View} from 'react-native';
|
||||||
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
import {useSafeAreaInsets} from 'react-native-safe-area-context';
|
||||||
|
import {getSession} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
const BottomNavbar = ({ navigation, routeName }) => {
|
const BottomNavbar = ({ navigation, routeName }) => {
|
||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
|
const session = getSession();
|
||||||
|
|
||||||
const tabs = [
|
const adminTabs = [
|
||||||
|
{
|
||||||
|
label: 'Game Settings',
|
||||||
|
route: 'GameSettingsScreen',
|
||||||
|
onPress: () => navigation.navigate('GameSettingsScreen'),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Map',
|
label: 'Map',
|
||||||
route: 'MapScreen',
|
route: 'MapScreen',
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'MapScreen' }),
|
onPress: () => navigation.navigate('MapScreen'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Players',
|
||||||
|
route: 'PlayersScreen',
|
||||||
|
onPress: () => navigation.navigate('PlayersScreen'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Leaderboard',
|
label: 'Leaderboard',
|
||||||
route: 'LeaderboardScreen',
|
route: 'LeaderboardScreen',
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'LeaderboardScreen' }),
|
onPress: () => navigation.navigate('LeaderboardScreen'),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const playerTabs = [
|
||||||
|
{
|
||||||
|
label: 'Map',
|
||||||
|
route: 'MapScreen',
|
||||||
|
onPress: () => navigation.navigate('MapScreen'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Leaderboard',
|
||||||
|
route: 'LeaderboardScreen',
|
||||||
|
onPress: () => navigation.navigate('LeaderboardScreen'),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Current Team',
|
label: 'Current Team',
|
||||||
route: 'TeamScreen',
|
route: 'TeamScreen',
|
||||||
onPress: () => navigation.navigate('Game', { screen: 'TeamScreen' }),
|
onPress: () => navigation.navigate('TeamScreen'),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const tabs = session.isAcceptedAdmin ? adminTabs : playerTabs;
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ 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 }) => {
|
const Screen = ({ children, style, showBack = false }) => {
|
||||||
// Initialisation -------------
|
// Initialisation -------------
|
||||||
|
|
||||||
const navigation = useNavigation();
|
const navigation = useNavigation();
|
||||||
@@ -24,13 +24,14 @@ const Screen = ({ children, style }) => {
|
|||||||
MapScreen: "Map",
|
MapScreen: "Map",
|
||||||
AdminHubScreen: "Admin Hub",
|
AdminHubScreen: "Admin Hub",
|
||||||
ManageSubgroupsScreen: "Manage Subgroups",
|
ManageSubgroupsScreen: "Manage Subgroups",
|
||||||
|
GameSettingsScreen: "Game Settings",
|
||||||
|
PlayersScreen: "Players",
|
||||||
};
|
};
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
const routeName = route.name;
|
const routeName = route.name;
|
||||||
const isAuthScreen = routeName === "LoginScreen" || routeName === "RegisterScreen";
|
const isAuthScreen = routeName === "LoginScreen" || routeName === "RegisterScreen";
|
||||||
const showBack = !isAuthScreen && navigation.canGoBack();
|
|
||||||
const pageTitle = titleMap[routeName] || "Treasure Hunt";
|
const pageTitle = titleMap[routeName] || "Treasure Hunt";
|
||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const AdminHubScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Admin Hub</Text>
|
<Text style={styles.title}>Admin Hub</Text>
|
||||||
<Text style={styles.body}>Admin hub setup goes here.</Text>
|
<Text style={styles.body}>Admin hub setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const CreateGameScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Create Game</Text>
|
<Text style={styles.title}>Create Game</Text>
|
||||||
<Text style={styles.body}>Create game setup goes here.</Text>
|
<Text style={styles.body}>Create game setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const GameLobbyScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Game Lobby</Text>
|
<Text style={styles.title}>Game Lobby</Text>
|
||||||
<Text style={styles.body}>Game lobby setup goes here.</Text>
|
<Text style={styles.body}>Game lobby setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -0,0 +1,174 @@
|
|||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {ActivityIndicator, StyleSheet, Switch, Text, TextInput, View} from 'react-native';
|
||||||
|
import Screen from '../layout/Screen';
|
||||||
|
import {Button, ButtonTray} from '../UI/Button';
|
||||||
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
|
import {getSession} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
|
const GameSettingsScreen = () => {
|
||||||
|
// Initialisation ------------
|
||||||
|
|
||||||
|
const session = getSession();
|
||||||
|
const {getLobby, updateGroup, getSubgroups} = useGameHook();
|
||||||
|
const isBusiness = Boolean(session.isBusiness);
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [groupName, setGroupName] = useState('');
|
||||||
|
const [teamsEnabled, setTeamsEnabled] = useState(false);
|
||||||
|
const [maxSubgroups, setMaxSubgroups] = useState('1');
|
||||||
|
const [adminJoinCode, setAdminJoinCode] = useState('');
|
||||||
|
const [memberJoinCode, setMemberJoinCode] = useState('');
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const load = async () => {
|
||||||
|
if (!session.currentGid) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const group = await getLobby(session.currentGid);
|
||||||
|
if (group) {
|
||||||
|
setGroupName(group.GroupName || '');
|
||||||
|
setTeamsEnabled(Boolean(group.TeamsEnabled));
|
||||||
|
setMaxSubgroups(String(group.MaxMemberSubgroups || 1));
|
||||||
|
setAdminJoinCode(group.AdminJoinCode || '');
|
||||||
|
}
|
||||||
|
const sgs = await getSubgroups(session.currentGid);
|
||||||
|
const memberSg = (sgs || []).find((sg) => !sg.IsAdminGroup);
|
||||||
|
if (memberSg) setMemberJoinCode(memberSg.JoinCode || '');
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
setSaving(true);
|
||||||
|
await updateGroup(session.currentGid, {
|
||||||
|
GroupName: groupName.trim(),
|
||||||
|
TeamsEnabled: teamsEnabled,
|
||||||
|
MaxMemberSubgroups: parseInt(maxSubgroups) || 1,
|
||||||
|
});
|
||||||
|
setSaving(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.currentGid) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Create or join a game first.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Screen>
|
||||||
|
<Text style={styles.sectionTitle}>Game Configuration</Text>
|
||||||
|
|
||||||
|
<Text style={styles.label}>Game Name</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={groupName}
|
||||||
|
onChangeText={setGroupName}
|
||||||
|
placeholder="Enter game name"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.toggleRow}>
|
||||||
|
<Text style={styles.label}>Teams Enabled</Text>
|
||||||
|
<Switch
|
||||||
|
value={teamsEnabled}
|
||||||
|
onValueChange={setTeamsEnabled}
|
||||||
|
trackColor={{false: '#d1d5db', true: '#93c5fd'}}
|
||||||
|
thumbColor={teamsEnabled ? '#2563eb' : '#9ca3af'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{isBusiness && (
|
||||||
|
<>
|
||||||
|
<Text style={styles.label}>Max Member Subgroups</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={maxSubgroups}
|
||||||
|
onChangeText={setMaxSubgroups}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="1"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.codeSection}>
|
||||||
|
<Text style={styles.codeLabel}>Admin Join Code</Text>
|
||||||
|
<Text style={styles.codeValue}>{adminJoinCode || '—'}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.codeSection}>
|
||||||
|
<Text style={styles.codeLabel}>Player Join Code</Text>
|
||||||
|
<Text style={styles.codeValue}>{memberJoinCode || '—'}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label={saving ? 'Saving...' : 'Save Settings'}
|
||||||
|
onClick={handleSave}
|
||||||
|
styleButton={styles.saveButton}
|
||||||
|
styleLabel={styles.saveLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
|
sectionTitle: {fontSize: 20, fontWeight: '700', color: '#1f2937', marginBottom: 15},
|
||||||
|
label: {fontSize: 14, fontWeight: '600', color: '#374151', marginBottom: 4, marginTop: 10},
|
||||||
|
body: {color: '#4b5563', fontSize: 15},
|
||||||
|
input: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#d1d5db',
|
||||||
|
borderRadius: 8,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
},
|
||||||
|
toggleRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10,
|
||||||
|
paddingVertical: 8,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#e5e7eb',
|
||||||
|
},
|
||||||
|
codeSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 14,
|
||||||
|
paddingVertical: 10,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
codeLabel: {fontSize: 14, fontWeight: '600', color: '#374151'},
|
||||||
|
codeValue: {fontSize: 16, fontWeight: '700', color: '#2563eb', letterSpacing: 2},
|
||||||
|
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
||||||
|
saveLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default GameSettingsScreen;
|
||||||
|
|
||||||
@@ -27,7 +27,7 @@ const GameTypeScreen = ({navigation}) => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Select Game Type</Text>
|
<Text style={styles.title}>Select Game Type</Text>
|
||||||
<View style={styles.content}>
|
<View style={styles.content}>
|
||||||
{gameTypes && gameTypes.map((type) => (
|
{gameTypes && gameTypes.map((type) => (
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const JoinPrivateGameScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Join Private Game</Text>
|
<Text style={styles.title}>Join Private Game</Text>
|
||||||
<Text style={styles.body}>Join private game setup goes here.</Text>
|
<Text style={styles.body}>Join private game setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ const ManageGameScreen = () => {
|
|||||||
|
|
||||||
if (!session.currentGid) {
|
if (!session.currentGid) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen showBack={true} style={styles.center}>
|
||||||
<Text style={styles.body}>Create or join a game first.</Text>
|
<Text style={styles.body}>Create or join a game first.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
@@ -97,7 +97,7 @@ const ManageGameScreen = () => {
|
|||||||
|
|
||||||
if (!session.isAcceptedAdmin) {
|
if (!session.isAcceptedAdmin) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen showBack={true} style={styles.center}>
|
||||||
<Text style={styles.body}>Only admins can edit cache locations.</Text>
|
<Text style={styles.body}>Only admins can edit cache locations.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
@@ -105,7 +105,7 @@ const ManageGameScreen = () => {
|
|||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen showBack={true} style={styles.center}>
|
||||||
<ActivityIndicator size="large"/>
|
<ActivityIndicator size="large"/>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
@@ -113,14 +113,14 @@ const ManageGameScreen = () => {
|
|||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen showBack={true} style={styles.center}>
|
||||||
<Text style={styles.error}>{error}</Text>
|
<Text style={styles.error}>{error}</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<View style={styles.mapWrap}>
|
<View style={styles.mapWrap}>
|
||||||
<AdminCacheEditorView
|
<AdminCacheEditorView
|
||||||
userLocation={userLocation}
|
userLocation={userLocation}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const ManagePrivateGameScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Manage Private Game</Text>
|
<Text style={styles.title}>Manage Private Game</Text>
|
||||||
<Text style={styles.body}>Manage private game setup goes here.</Text>
|
<Text style={styles.body}>Manage private game setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ const ManageSubgroupsScreen = () => {
|
|||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.container}>
|
<Screen showBack={true} style={styles.container}>
|
||||||
<Text style={styles.title}>Manage Subgroups</Text>
|
<Text style={styles.title}>Manage Subgroups</Text>
|
||||||
<Text style={styles.body}>Manage subgroups setup goes here.</Text>
|
<Text style={styles.body}>Manage subgroups setup goes here.</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
|
|||||||
@@ -1,14 +1,18 @@
|
|||||||
import React, {useCallback, useEffect, useState} from 'react';
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
import {ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, View} from 'react-native';
|
import {ActivityIndicator, ScrollView, StyleSheet, Text, TextInput, View} from 'react-native';
|
||||||
|
import MapView, {Circle, Marker} from 'react-native-maps';
|
||||||
import * as Location from 'expo-location';
|
import * as Location from 'expo-location';
|
||||||
import {Magnetometer} from 'expo-sensors';
|
import {Magnetometer} from 'expo-sensors';
|
||||||
import Screen from '../layout/Screen';
|
import Screen from '../layout/Screen';
|
||||||
import {Button, ButtonTray} from '../UI/Button';
|
import {Button, ButtonTray} from '../UI/Button';
|
||||||
|
import CacheCardItem from '../gameplay/CacheCardItem';
|
||||||
import PlayerMapView from '../gameplay/PlayerMapView';
|
import PlayerMapView from '../gameplay/PlayerMapView';
|
||||||
import useGameHook from '../../hooks/useGameHook';
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
import usePlayerGame from '../../hooks/usePlayerGame';
|
import usePlayerGame from '../../hooks/usePlayerGame';
|
||||||
import {getSession, setSessionGroup, setSessionUser} from '../../hooks/SessionStore';
|
import {getSession, setSessionGroup, setSessionUser} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
|
const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.05, longitudeDelta: 0.05};
|
||||||
|
|
||||||
const toHeading = ({x, y}) => {
|
const toHeading = ({x, y}) => {
|
||||||
const angle = Math.atan2(y, x) * (180 / Math.PI);
|
const angle = Math.atan2(y, x) * (180 / Math.PI);
|
||||||
return angle < 0 ? angle + 360 : angle;
|
return angle < 0 ? angle + 360 : angle;
|
||||||
@@ -18,7 +22,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const {getCaches, claimCache, joinPrivateGame, createPrivateGame, getLobby, getUser} = useGameHook();
|
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, createPrivateGame, getLobby, getUser} = useGameHook();
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -26,12 +30,19 @@ const MapScreen = ({navigation}) => {
|
|||||||
const [isAdmin, setIsAdmin] = useState(session.isAcceptedAdmin);
|
const [isAdmin, setIsAdmin] = useState(session.isAcceptedAdmin);
|
||||||
const [userLocation, setUserLocation] = useState(null);
|
const [userLocation, setUserLocation] = useState(null);
|
||||||
const [heading, setHeading] = useState(null);
|
const [heading, setHeading] = useState(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [playerLoading, setPlayerLoading] = useState(true);
|
||||||
const [cacheRecords, setCacheRecords] = useState([]);
|
const [cacheRecords, setCacheRecords] = useState([]);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
const [gameCode, setGameCode] = useState('');
|
const [gameCode, setGameCode] = useState('');
|
||||||
const [groupInfo, setGroupInfo] = useState(null);
|
const [groupInfo, setGroupInfo] = useState(null);
|
||||||
|
|
||||||
|
// Admin cache creation / editing state
|
||||||
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
|
const [editingCacheId, setEditingCacheId] = useState(null);
|
||||||
|
const [newCoord, setNewCoord] = useState(null);
|
||||||
|
const [newClue, setNewClue] = useState('');
|
||||||
|
const [newRadius, setNewRadius] = useState('20');
|
||||||
|
|
||||||
const isPlayer = inGame && !isAdmin;
|
const isPlayer = inGame && !isAdmin;
|
||||||
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(
|
const {visibleCache, isClaiming, setIsClaiming} = usePlayerGame(
|
||||||
isPlayer ? userLocation : null,
|
isPlayer ? userLocation : null,
|
||||||
@@ -39,32 +50,32 @@ const MapScreen = ({navigation}) => {
|
|||||||
isPlayer ? cacheRecords : [],
|
isPlayer ? cacheRecords : [],
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const mapRegion = userLocation
|
||||||
|
? {...userLocation, latitudeDelta: 0.01, longitudeDelta: 0.01}
|
||||||
|
: DEFAULT_REGION;
|
||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|
||||||
// Load group info for TeamsEnabled check
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!session.currentGid) return;
|
if (!session.currentGid) return;
|
||||||
getLobby(session.currentGid).then(setGroupInfo);
|
getLobby(session.currentGid).then(setGroupInfo);
|
||||||
}, [inGame]);
|
}, [inGame]);
|
||||||
|
|
||||||
// Load caches for players only
|
|
||||||
const loadCaches = useCallback(async () => {
|
const loadCaches = useCallback(async () => {
|
||||||
if (!session.currentGid || isAdmin) {
|
if (!session.currentGid) {
|
||||||
setCacheRecords([]);
|
setCacheRecords([]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const rows = await getCaches(session.currentGid, session.currentSGid);
|
const sgFilter = isAdmin ? null : session.currentSGid;
|
||||||
|
const rows = await getCaches(session.currentGid, sgFilter);
|
||||||
setCacheRecords(rows || []);
|
setCacheRecords(rows || []);
|
||||||
}, [inGame, isAdmin]);
|
}, [inGame, isAdmin]);
|
||||||
|
|
||||||
useEffect(() => { loadCaches(); }, [loadCaches]);
|
useEffect(() => { loadCaches(); }, [loadCaches]);
|
||||||
|
|
||||||
// Location + compass tracking for players only
|
// Location tracking
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isPlayer) {
|
if (!inGame) return;
|
||||||
setLoading(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let locationSub;
|
let locationSub;
|
||||||
let headingSub;
|
let headingSub;
|
||||||
|
|
||||||
@@ -72,7 +83,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
const {status} = await Location.requestForegroundPermissionsAsync();
|
const {status} = await Location.requestForegroundPermissionsAsync();
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
setError('Location permission denied');
|
setError('Location permission denied');
|
||||||
setLoading(false);
|
setPlayerLoading(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const current = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Balanced});
|
const current = await Location.getCurrentPositionAsync({accuracy: Location.Accuracy.Balanced});
|
||||||
@@ -83,9 +94,11 @@ const MapScreen = ({navigation}) => {
|
|||||||
(next) => setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}),
|
(next) => setUserLocation({latitude: next.coords.latitude, longitude: next.coords.longitude}),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (isPlayer) {
|
||||||
Magnetometer.setUpdateInterval(500);
|
Magnetometer.setUpdateInterval(500);
|
||||||
headingSub = Magnetometer.addListener((data) => setHeading(toHeading(data)));
|
headingSub = Magnetometer.addListener((data) => setHeading(toHeading(data)));
|
||||||
setLoading(false);
|
}
|
||||||
|
setPlayerLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
start();
|
start();
|
||||||
@@ -93,7 +106,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
if (locationSub) locationSub.remove();
|
if (locationSub) locationSub.remove();
|
||||||
if (headingSub) headingSub.remove();
|
if (headingSub) headingSub.remove();
|
||||||
};
|
};
|
||||||
}, [isPlayer]);
|
}, [inGame, isPlayer]);
|
||||||
|
|
||||||
const handleJoinGame = async () => {
|
const handleJoinGame = async () => {
|
||||||
if (!gameCode.trim()) return;
|
if (!gameCode.trim()) return;
|
||||||
@@ -118,6 +131,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
setSessionUser(freshUser);
|
setSessionUser(freshUser);
|
||||||
setInGame(true);
|
setInGame(true);
|
||||||
setIsAdmin(true);
|
setIsAdmin(true);
|
||||||
|
navigation.navigate('GameSettingsScreen');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -133,13 +147,67 @@ const MapScreen = ({navigation}) => {
|
|||||||
await loadCaches();
|
await loadCaches();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Admin — open create form
|
||||||
|
const handleCreateCachePress = () => {
|
||||||
|
const fallback = userLocation || {latitude: mapRegion.latitude, longitude: mapRegion.longitude};
|
||||||
|
setEditingCacheId(null);
|
||||||
|
setNewCoord(fallback);
|
||||||
|
setNewClue('');
|
||||||
|
setNewRadius('20');
|
||||||
|
setIsCreating(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Admin — open edit form
|
||||||
|
const handleEditCache = (cache) => {
|
||||||
|
setEditingCacheId(cache.id);
|
||||||
|
setNewCoord(cache.coordinates);
|
||||||
|
setNewClue(cache.clue || '');
|
||||||
|
setNewRadius(String(cache.radius || 20));
|
||||||
|
setIsCreating(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Admin — delete cache
|
||||||
|
const handleDeleteCache = async (cache) => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
await deleteCache(session.currentGid, cache.id);
|
||||||
|
await loadCaches();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Admin — save create or edit
|
||||||
|
const handleSaveCache = async () => {
|
||||||
|
if (!newCoord || !newClue.trim() || !session.currentGid) return;
|
||||||
|
const payload = {
|
||||||
|
gid: session.currentGid,
|
||||||
|
latitude: newCoord.latitude,
|
||||||
|
longitude: newCoord.longitude,
|
||||||
|
radius: parseInt(newRadius) || 20,
|
||||||
|
clue: newClue.trim(),
|
||||||
|
subgroupId: 1,
|
||||||
|
};
|
||||||
|
if (editingCacheId) payload.cacheId = editingCacheId;
|
||||||
|
await upsertCache(payload);
|
||||||
|
setIsCreating(false);
|
||||||
|
setEditingCacheId(null);
|
||||||
|
await loadCaches();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleCancelCreate = () => {
|
||||||
|
setIsCreating(false);
|
||||||
|
setEditingCacheId(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Player — select cache (scroll to / highlight)
|
||||||
|
const handleSelectCache = (cache) => {
|
||||||
|
// Future: scroll map to cache location
|
||||||
|
};
|
||||||
|
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
// Not in a game
|
// Not in a game
|
||||||
if (!inGame) {
|
if (!inGame) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.center}>
|
||||||
<View style={styles.row}>
|
<View style={styles.inputRow}>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.codeInput}
|
style={styles.codeInput}
|
||||||
placeholder="Enter Game Code"
|
placeholder="Enter Game Code"
|
||||||
@@ -154,6 +222,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
styleLabel={styles.joinLabel}
|
styleLabel={styles.joinLabel}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
<View style={styles.fullRow}>
|
||||||
<ButtonTray>
|
<ButtonTray>
|
||||||
<Button
|
<Button
|
||||||
label="Create a Game"
|
label="Create a Game"
|
||||||
@@ -162,29 +231,170 @@ const MapScreen = ({navigation}) => {
|
|||||||
styleLabel={styles.createLabel}
|
styleLabel={styles.createLabel}
|
||||||
/>
|
/>
|
||||||
</ButtonTray>
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Admin view
|
// Admin — creating / editing a cache
|
||||||
|
if (isAdmin && isCreating) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.containerMap}>
|
||||||
|
<View style={styles.mapWrap}>
|
||||||
|
<MapView
|
||||||
|
style={{flex: 1}}
|
||||||
|
initialRegion={newCoord ? {...newCoord, latitudeDelta: 0.01, longitudeDelta: 0.01} : mapRegion}
|
||||||
|
scrollEnabled={true}
|
||||||
|
zoomEnabled={true}
|
||||||
|
onPress={(e) => setNewCoord(e.nativeEvent.coordinate)}
|
||||||
|
showsUserLocation
|
||||||
|
>
|
||||||
|
{cacheRecords.map((cache) => (
|
||||||
|
<React.Fragment key={cache.id}>
|
||||||
|
<Marker coordinate={cache.coordinates} pinColor="#9ca3af" title={cache.clue}/>
|
||||||
|
<Circle
|
||||||
|
center={cache.coordinates}
|
||||||
|
radius={cache.radius}
|
||||||
|
fillColor="rgba(156,163,175,0.15)"
|
||||||
|
strokeColor="rgba(156,163,175,0.6)"
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
{newCoord && (
|
||||||
|
<>
|
||||||
|
<Marker
|
||||||
|
coordinate={newCoord}
|
||||||
|
draggable
|
||||||
|
tracksViewChanges={false}
|
||||||
|
pinColor="#2563eb"
|
||||||
|
title={editingCacheId ? 'Edit Cache' : 'New Cache'}
|
||||||
|
onDragEnd={(e) => setNewCoord(e.nativeEvent.coordinate)}
|
||||||
|
/>
|
||||||
|
<Circle
|
||||||
|
center={newCoord}
|
||||||
|
radius={parseInt(newRadius) || 20}
|
||||||
|
fillColor="rgba(37,99,235,0.15)"
|
||||||
|
strokeColor="rgba(37,99,235,0.85)"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</MapView>
|
||||||
|
</View>
|
||||||
|
<ScrollView style={styles.formSection}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.formInput}
|
||||||
|
placeholder="Cache's Clue"
|
||||||
|
value={newClue}
|
||||||
|
onChangeText={setNewClue}
|
||||||
|
/>
|
||||||
|
<View style={styles.coordRow}>
|
||||||
|
<View style={styles.coordField}>
|
||||||
|
<Text style={styles.coordLabel}>Latitude</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.formInput}
|
||||||
|
placeholder="Latitude"
|
||||||
|
value={newCoord ? String(newCoord.latitude.toFixed(6)) : ''}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.coordField}>
|
||||||
|
<Text style={styles.coordLabel}>Longitude</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.formInput}
|
||||||
|
placeholder="Longitude"
|
||||||
|
value={newCoord ? String(newCoord.longitude.toFixed(6)) : ''}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
<TextInput
|
||||||
|
style={styles.formInput}
|
||||||
|
placeholder="Claim Radius (metres)"
|
||||||
|
value={newRadius}
|
||||||
|
onChangeText={setNewRadius}
|
||||||
|
keyboardType="numeric"
|
||||||
|
/>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label={editingCacheId ? 'Update Cache' : 'Save Cache'}
|
||||||
|
onClick={handleSaveCache}
|
||||||
|
styleButton={styles.saveButton}
|
||||||
|
styleLabel={styles.saveLabel}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Cancel"
|
||||||
|
onClick={handleCancelCreate}
|
||||||
|
styleButton={styles.cancelButton}
|
||||||
|
styleLabel={styles.cancelLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</ScrollView>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Admin — normal view (map + create button + cache list)
|
||||||
if (isAdmin) {
|
if (isAdmin) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.containerMap}>
|
||||||
<Text style={styles.body}>Admins manage caches from Manage Game.</Text>
|
<View style={styles.mapWrap}>
|
||||||
|
<MapView
|
||||||
|
style={{flex: 1}}
|
||||||
|
initialRegion={mapRegion}
|
||||||
|
showsUserLocation
|
||||||
|
>
|
||||||
|
{cacheRecords.map((cache) => (
|
||||||
|
<React.Fragment key={cache.id}>
|
||||||
|
<Marker coordinate={cache.coordinates} title={cache.clue}/>
|
||||||
|
<Circle
|
||||||
|
center={cache.coordinates}
|
||||||
|
radius={cache.radius}
|
||||||
|
fillColor="rgba(59,130,246,0.15)"
|
||||||
|
strokeColor="rgba(59,130,246,0.85)"
|
||||||
|
/>
|
||||||
|
</React.Fragment>
|
||||||
|
))}
|
||||||
|
</MapView>
|
||||||
|
</View>
|
||||||
|
<View style={styles.createCacheWrap}>
|
||||||
|
<Button
|
||||||
|
label="Create Cache"
|
||||||
|
onClick={handleCreateCachePress}
|
||||||
|
styleButton={styles.createCacheButton}
|
||||||
|
styleLabel={styles.createCacheLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.cacheSection}>
|
||||||
|
<ScrollView>
|
||||||
|
{cacheRecords.map((cache) => (
|
||||||
|
<CacheCardItem
|
||||||
|
key={cache.id}
|
||||||
|
cache={cache}
|
||||||
|
isAdmin={true}
|
||||||
|
onEdit={handleEditCache}
|
||||||
|
onDelete={handleDeleteCache}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
{cacheRecords.length === 0 && (
|
||||||
|
<Text style={styles.emptyText}>No caches yet. Tap Create Cache to add one.</Text>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</View>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Loading
|
// Player — loading location
|
||||||
if (loading) {
|
if (playerLoading) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.center}>
|
||||||
<ActivityIndicator size="large"/>
|
<ActivityIndicator size="large"/>
|
||||||
|
<Text style={styles.loadingText}>Getting your location...</Text>
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Error
|
// Player — error
|
||||||
if (error) {
|
if (error) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.center}>
|
||||||
@@ -212,12 +422,12 @@ const MapScreen = ({navigation}) => {
|
|||||||
<View style={styles.cacheSection}>
|
<View style={styles.cacheSection}>
|
||||||
<ScrollView>
|
<ScrollView>
|
||||||
{cacheRecords.map((cache) => (
|
{cacheRecords.map((cache) => (
|
||||||
<View key={cache.id} style={styles.cacheCard}>
|
<CacheCardItem
|
||||||
<Text style={styles.cacheClue}>{cache.clue}</Text>
|
key={cache.id}
|
||||||
<Text style={[styles.cacheStatus, cache.ClaimedByUid && styles.cacheStatusClaimed]}>
|
cache={cache}
|
||||||
{cache.ClaimedByUid ? 'Claimed' : 'Available'}
|
isAdmin={false}
|
||||||
</Text>
|
onSelect={handleSelectCache}
|
||||||
</View>
|
/>
|
||||||
))}
|
))}
|
||||||
{cacheRecords.length === 0 && (
|
{cacheRecords.length === 0 && (
|
||||||
<Text style={styles.emptyText}>No caches available yet.</Text>
|
<Text style={styles.emptyText}>No caches available yet.</Text>
|
||||||
@@ -231,10 +441,11 @@ const MapScreen = ({navigation}) => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
center: {justifyContent: 'center', alignItems: 'center'},
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
containerMap: {padding: 0},
|
containerMap: {padding: 0},
|
||||||
mapWrap: {flex: 2},
|
mapWrap: {flex: 1},
|
||||||
body: {color: '#4b5563', fontSize: 15},
|
|
||||||
error: {color: '#dc2626', fontSize: 15},
|
error: {color: '#dc2626', fontSize: 15},
|
||||||
row: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
loadingText: {color: '#6b7280', fontSize: 14, marginTop: 10},
|
||||||
|
inputRow: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
||||||
|
fullRow: {width: '100%', paddingHorizontal: 20},
|
||||||
codeInput: {
|
codeInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
@@ -255,26 +466,33 @@ const styles = StyleSheet.create({
|
|||||||
paddingVertical: 8,
|
paddingVertical: 8,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
},
|
},
|
||||||
|
createCacheWrap: {paddingHorizontal: 12, paddingVertical: 8},
|
||||||
|
createCacheButton: {backgroundColor: '#2563eb', borderColor: '#2563eb'},
|
||||||
|
createCacheLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
formSection: {flex: 1, paddingHorizontal: 12, paddingVertical: 10},
|
||||||
|
formInput: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#d1d5db',
|
||||||
|
borderRadius: 8,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
fontSize: 16,
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
marginBottom: 10,
|
||||||
|
},
|
||||||
|
coordRow: {flexDirection: 'row', gap: 10},
|
||||||
|
coordField: {flex: 1},
|
||||||
|
coordLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 4},
|
||||||
|
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
||||||
|
saveLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
cancelButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'},
|
||||||
|
cancelLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
cacheSection: {
|
cacheSection: {
|
||||||
flex: 1,
|
maxHeight: 160,
|
||||||
backgroundColor: '#f3f4f6',
|
backgroundColor: '#f3f4f6',
|
||||||
paddingHorizontal: 12,
|
paddingHorizontal: 12,
|
||||||
paddingTop: 10,
|
paddingTop: 10,
|
||||||
},
|
},
|
||||||
cacheCard: {
|
|
||||||
backgroundColor: '#ffffff',
|
|
||||||
borderRadius: 8,
|
|
||||||
padding: 12,
|
|
||||||
marginBottom: 8,
|
|
||||||
flexDirection: 'row',
|
|
||||||
justifyContent: 'space-between',
|
|
||||||
alignItems: 'center',
|
|
||||||
borderWidth: 1,
|
|
||||||
borderColor: '#e5e7eb',
|
|
||||||
},
|
|
||||||
cacheClue: {fontSize: 15, fontWeight: '600', color: '#1f2937'},
|
|
||||||
cacheStatus: {fontSize: 13, color: '#16a34a', fontWeight: '600'},
|
|
||||||
cacheStatusClaimed: {color: '#9ca3af'},
|
|
||||||
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 20, fontSize: 14},
|
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 20, fontSize: 14},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,244 @@
|
|||||||
|
import React, {useCallback, useEffect, useState} from 'react';
|
||||||
|
import {ActivityIndicator, Pressable, ScrollView, StyleSheet, Text, View} from 'react-native';
|
||||||
|
import Screen from '../layout/Screen';
|
||||||
|
import Card from '../UI/Card';
|
||||||
|
import {Button} from '../UI/Button';
|
||||||
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
|
import {getSession} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
|
const PlayersScreen = () => {
|
||||||
|
// Initialisation ------------
|
||||||
|
|
||||||
|
const session = getSession();
|
||||||
|
const {getLobby, getTeams, getTeamMembers, getGroupMembers, getUser, getCaches} = useGameHook();
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [groupInfo, setGroupInfo] = useState(null);
|
||||||
|
const [teams, setTeams] = useState([]);
|
||||||
|
const [players, setPlayers] = useState([]);
|
||||||
|
const [selectedTeam, setSelectedTeam] = useState(null);
|
||||||
|
const [teamMembers, setTeamMembers] = useState([]);
|
||||||
|
const [memberNames, setMemberNames] = useState({});
|
||||||
|
const [memberCacheCounts, setMemberCacheCounts] = useState({});
|
||||||
|
const [detailLoading, setDetailLoading] = useState(false);
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
|
const loadData = useCallback(async () => {
|
||||||
|
if (!session.currentGid) {
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const group = await getLobby(session.currentGid);
|
||||||
|
setGroupInfo(group);
|
||||||
|
|
||||||
|
if (group && group.TeamsEnabled) {
|
||||||
|
// Teams enabled — load all teams for this group
|
||||||
|
const teamRows = await getTeams(session.currentGid);
|
||||||
|
setTeams(teamRows || []);
|
||||||
|
} else {
|
||||||
|
// Teams disabled — load all members for this group
|
||||||
|
const memberships = await getGroupMembers(session.currentGid);
|
||||||
|
const nonAdminMembers = (memberships || []).filter((m) => !m.IsAcceptedAdmin);
|
||||||
|
const playerList = [];
|
||||||
|
for (const m of nonAdminMembers) {
|
||||||
|
const user = await getUser(m.Uid);
|
||||||
|
if (user) playerList.push(user);
|
||||||
|
}
|
||||||
|
setPlayers(playerList);
|
||||||
|
}
|
||||||
|
setLoading(false);
|
||||||
|
}, [session.currentGid]);
|
||||||
|
|
||||||
|
useEffect(() => { loadData(); }, [loadData]);
|
||||||
|
|
||||||
|
const handleSelectTeam = async (team) => {
|
||||||
|
setSelectedTeam(team);
|
||||||
|
setDetailLoading(true);
|
||||||
|
|
||||||
|
// Load team members and their usernames
|
||||||
|
const ms = await getTeamMembers(team.Tid);
|
||||||
|
setTeamMembers(ms || []);
|
||||||
|
|
||||||
|
const names = {};
|
||||||
|
for (const m of (ms || [])) {
|
||||||
|
const user = await getUser(m.Uid);
|
||||||
|
if (user) names[m.Uid] = user.username;
|
||||||
|
}
|
||||||
|
setMemberNames(names);
|
||||||
|
|
||||||
|
// Load all caches to count individual contributions
|
||||||
|
const caches = await getCaches(session.currentGid, null);
|
||||||
|
const counts = {};
|
||||||
|
for (const m of (ms || [])) {
|
||||||
|
counts[m.Uid] = (caches || []).filter((c) => c.ClaimedByUid === m.Uid).length;
|
||||||
|
}
|
||||||
|
setMemberCacheCounts(counts);
|
||||||
|
setDetailLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackToTeams = () => {
|
||||||
|
setSelectedTeam(null);
|
||||||
|
setTeamMembers([]);
|
||||||
|
setMemberNames({});
|
||||||
|
setMemberCacheCounts({});
|
||||||
|
};
|
||||||
|
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.currentGid) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.body}>Create or join a game first.</Text>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teams enabled — show team detail view
|
||||||
|
if (groupInfo?.TeamsEnabled && selectedTeam) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<View style={styles.teamDetailHeader}>
|
||||||
|
<Text style={styles.teamDetailName}>{selectedTeam.TeamName || 'Unnamed Team'}</Text>
|
||||||
|
<Text style={styles.teamDetailCode}>Code: {selectedTeam.JoinCode}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{detailLoading ? (
|
||||||
|
<View style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</View>
|
||||||
|
) : (
|
||||||
|
<ScrollView style={styles.listSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Team Members</Text>
|
||||||
|
{teamMembers.map((m) => (
|
||||||
|
<Card key={m.id}>
|
||||||
|
<View style={styles.memberRow}>
|
||||||
|
<Text style={styles.memberName}>
|
||||||
|
{memberNames[m.Uid] || `Player ${m.Uid}`}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.memberCaches}>
|
||||||
|
{memberCacheCounts[m.Uid] || 0} cache(s) claimed
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{teamMembers.length === 0 && (
|
||||||
|
<Text style={styles.emptyText}>No members in this team.</Text>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<View style={styles.backWrap}>
|
||||||
|
<Button
|
||||||
|
label="Back to Teams"
|
||||||
|
onClick={handleBackToTeams}
|
||||||
|
styleButton={styles.backButton}
|
||||||
|
styleLabel={styles.backLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teams enabled — show team cards
|
||||||
|
if (groupInfo?.TeamsEnabled) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.sectionTitle}>Teams</Text>
|
||||||
|
<ScrollView style={styles.listSection}>
|
||||||
|
{teams.map((team) => (
|
||||||
|
<Pressable key={team.Tid} onPress={() => handleSelectTeam(team)}>
|
||||||
|
<Card>
|
||||||
|
<View style={styles.teamCardRow}>
|
||||||
|
<Text style={styles.teamName}>
|
||||||
|
{team.TeamName || 'Unnamed Team'}
|
||||||
|
</Text>
|
||||||
|
<Text style={styles.teamCode}>
|
||||||
|
Code: {team.JoinCode}
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
</Pressable>
|
||||||
|
))}
|
||||||
|
{teams.length === 0 && (
|
||||||
|
<Text style={styles.emptyText}>No teams have been created yet.</Text>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Teams disabled — show player list
|
||||||
|
return (
|
||||||
|
<Screen style={styles.container}>
|
||||||
|
<Text style={styles.sectionTitle}>Players</Text>
|
||||||
|
<ScrollView style={styles.listSection}>
|
||||||
|
{players.map((player) => (
|
||||||
|
<Card key={player.Uid}>
|
||||||
|
<Text style={styles.playerName}>{player.username}</Text>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{players.length === 0 && (
|
||||||
|
<Text style={styles.emptyText}>No players have joined yet.</Text>
|
||||||
|
)}
|
||||||
|
</ScrollView>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
center: {justifyContent: 'center', alignItems: 'center', flex: 1},
|
||||||
|
container: {padding: 0},
|
||||||
|
body: {color: '#4b5563', fontSize: 15},
|
||||||
|
sectionTitle: {
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: '700',
|
||||||
|
color: '#1f2937',
|
||||||
|
marginBottom: 10,
|
||||||
|
paddingHorizontal: 15,
|
||||||
|
paddingTop: 15,
|
||||||
|
},
|
||||||
|
listSection: {flex: 1, paddingHorizontal: 15},
|
||||||
|
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14},
|
||||||
|
// Team cards
|
||||||
|
teamCardRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
teamName: {fontSize: 16, fontWeight: '600', color: '#1f2937'},
|
||||||
|
teamCode: {fontSize: 14, fontWeight: '700', color: '#2563eb', letterSpacing: 1},
|
||||||
|
// Team detail
|
||||||
|
teamDetailHeader: {
|
||||||
|
backgroundColor: '#374151',
|
||||||
|
paddingVertical: 14,
|
||||||
|
paddingHorizontal: 16,
|
||||||
|
},
|
||||||
|
teamDetailName: {fontSize: 18, fontWeight: '700', color: '#ffffff', marginBottom: 4},
|
||||||
|
teamDetailCode: {fontSize: 14, fontWeight: '600', color: '#93c5fd', letterSpacing: 1},
|
||||||
|
memberRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
},
|
||||||
|
memberName: {fontSize: 16, fontWeight: '600', color: '#1f2937'},
|
||||||
|
memberCaches: {fontSize: 13, fontWeight: '600', color: '#16a34a'},
|
||||||
|
backWrap: {padding: 15},
|
||||||
|
backButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'},
|
||||||
|
backLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
// Player list
|
||||||
|
playerName: {fontSize: 16, fontWeight: '600', color: '#1f2937'},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default PlayersScreen;
|
||||||
|
|
||||||
@@ -9,6 +9,7 @@ const TeamScreen = () => {
|
|||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
|
const isAdmin = session.isAcceptedAdmin;
|
||||||
const {getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser} = useGameHook();
|
const {getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser} = useGameHook();
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
@@ -86,7 +87,7 @@ const TeamScreen = () => {
|
|||||||
if (!activeTid || !team) {
|
if (!activeTid || !team) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.center}>
|
||||||
<View style={styles.row}>
|
<View style={styles.inputRow}>
|
||||||
<TextInput
|
<TextInput
|
||||||
style={styles.codeInput}
|
style={styles.codeInput}
|
||||||
placeholder="Enter Team Code"
|
placeholder="Enter Team Code"
|
||||||
@@ -101,6 +102,8 @@ const TeamScreen = () => {
|
|||||||
styleLabel={styles.joinLabel}
|
styleLabel={styles.joinLabel}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
|
{!isAdmin && (
|
||||||
|
<View style={styles.fullRow}>
|
||||||
<ButtonTray>
|
<ButtonTray>
|
||||||
<Button
|
<Button
|
||||||
label="Create Team"
|
label="Create Team"
|
||||||
@@ -109,6 +112,8 @@ const TeamScreen = () => {
|
|||||||
styleLabel={styles.createLabel}
|
styleLabel={styles.createLabel}
|
||||||
/>
|
/>
|
||||||
</ButtonTray>
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
</Screen>
|
</Screen>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -145,7 +150,8 @@ const TeamScreen = () => {
|
|||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
center: {justifyContent: 'center', alignItems: 'center'},
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
container: {padding: 0},
|
container: {padding: 0},
|
||||||
row: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
inputRow: {flexDirection: 'row', gap: 10, marginBottom: 15, width: '100%', paddingHorizontal: 20},
|
||||||
|
fullRow: {width: '100%', paddingHorizontal: 20},
|
||||||
codeInput: {
|
codeInput: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
borderWidth: 1,
|
borderWidth: 1,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ const useGameHook = () => {
|
|||||||
const usersEndpoint = `${API_BASE_URL}/users`;
|
const usersEndpoint = `${API_BASE_URL}/users`;
|
||||||
const gameTypesEndpoint = `${API_BASE_URL}/game-types`;
|
const gameTypesEndpoint = `${API_BASE_URL}/game-types`;
|
||||||
const groupsEndpoint = `${API_BASE_URL}/groups`;
|
const groupsEndpoint = `${API_BASE_URL}/groups`;
|
||||||
|
const subgroupsEndpoint = `${API_BASE_URL}/subgroups`;
|
||||||
const subgroupMembershipsEndpoint = `${API_BASE_URL}/subgroup-memberships`;
|
const subgroupMembershipsEndpoint = `${API_BASE_URL}/subgroup-memberships`;
|
||||||
const teamsEndpoint = `${API_BASE_URL}/teams`;
|
const teamsEndpoint = `${API_BASE_URL}/teams`;
|
||||||
const teamMembersEndpoint = `${API_BASE_URL}/team-members`;
|
const teamMembersEndpoint = `${API_BASE_URL}/team-members`;
|
||||||
@@ -47,18 +48,38 @@ const useGameHook = () => {
|
|||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateGroup = async (gid, data) => {
|
||||||
|
const response = await API.put(`${groupsEndpoint}/${gid}`, data);
|
||||||
|
return response.isSuccess ? response.result : null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSubgroups = async (gid) => {
|
||||||
|
const response = await API.get(`${subgroupsEndpoint}?Gid=${gid}`);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
// Subgroup memberships
|
// Subgroup memberships
|
||||||
const joinPrivateGame = async (payload) => {
|
const joinPrivateGame = async (payload) => {
|
||||||
const response = await API.post(subgroupMembershipsEndpoint, payload);
|
const response = await API.post(subgroupMembershipsEndpoint, payload);
|
||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getGroupMembers = async (gid) => {
|
||||||
|
const response = await API.get(`${subgroupMembershipsEndpoint}?Gid=${gid}`);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
const joinAsAdmin = async (payload) => {
|
const joinAsAdmin = async (payload) => {
|
||||||
const response = await API.post(adminWaitlistEndpoint, payload);
|
const response = await API.post(adminWaitlistEndpoint, payload);
|
||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Teams
|
// Teams
|
||||||
|
const getTeams = async (gid) => {
|
||||||
|
const response = await API.get(`${teamsEndpoint}?Gid=${gid}`);
|
||||||
|
return response.isSuccess ? response.result : [];
|
||||||
|
};
|
||||||
|
|
||||||
const getTeam = async (tid) => {
|
const getTeam = async (tid) => {
|
||||||
const response = await API.get(`${teamsEndpoint}/${tid}`);
|
const response = await API.get(`${teamsEndpoint}/${tid}`);
|
||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
@@ -116,6 +137,11 @@ const useGameHook = () => {
|
|||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const deleteCache = async (gid, cacheId) => {
|
||||||
|
const response = await API.delete(`${gameDataEndpoint}/${gid}/caches/${cacheId}`);
|
||||||
|
return response.isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
// Return ---------------------
|
// Return ---------------------
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -125,8 +151,12 @@ const useGameHook = () => {
|
|||||||
getCreatedPrivateGame,
|
getCreatedPrivateGame,
|
||||||
createPrivateGame,
|
createPrivateGame,
|
||||||
getLobby,
|
getLobby,
|
||||||
|
updateGroup,
|
||||||
|
getSubgroups,
|
||||||
joinPrivateGame,
|
joinPrivateGame,
|
||||||
joinAsAdmin,
|
joinAsAdmin,
|
||||||
|
getGroupMembers,
|
||||||
|
getTeams,
|
||||||
getTeam,
|
getTeam,
|
||||||
createTeam,
|
createTeam,
|
||||||
joinTeamByCode,
|
joinTeamByCode,
|
||||||
@@ -135,6 +165,7 @@ const useGameHook = () => {
|
|||||||
getCaches,
|
getCaches,
|
||||||
upsertCache,
|
upsertCache,
|
||||||
claimCache,
|
claimCache,
|
||||||
|
deleteCache,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user