mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-08 03:00:20 +00:00
updating business components.
This commit is contained in:
1 parent
e3564bea08
commit
e0065e885f
8 files changed
+659
-110
No files matched your search
@@ -9,6 +9,7 @@ import TeamScreen from "./src/components/screens/TeamScreen";
|
|||||||
import MapScreen from "./src/components/screens/MapScreen";
|
import MapScreen from "./src/components/screens/MapScreen";
|
||||||
import LeaderboardScreen from "./src/components/screens/LeaderboardScreen";
|
import LeaderboardScreen from "./src/components/screens/LeaderboardScreen";
|
||||||
import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
|
import GameSettingsScreen from "./src/components/screens/GameSettingsScreen";
|
||||||
|
import DepartmentSettingsScreen from "./src/components/screens/DepartmentSettingsScreen";
|
||||||
import PlayersScreen from "./src/components/screens/PlayersScreen";
|
import PlayersScreen from "./src/components/screens/PlayersScreen";
|
||||||
import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen";
|
import ExpandedMapScreen from "./src/components/screens/ExpandedMapScreen";
|
||||||
import {loadSession} from "./src/hooks/SessionStore";
|
import {loadSession} from "./src/hooks/SessionStore";
|
||||||
@@ -43,6 +44,7 @@ const GameStack = () => (
|
|||||||
const AdminStack = () => (
|
const AdminStack = () => (
|
||||||
<AdminStackNav.Navigator screenOptions={screenOptions}>
|
<AdminStackNav.Navigator screenOptions={screenOptions}>
|
||||||
<AdminStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
|
<AdminStackNav.Screen name="GameSettingsScreen" component={GameSettingsScreen}/>
|
||||||
|
<AdminStackNav.Screen name="DepartmentSettingsScreen" component={DepartmentSettingsScreen}/>
|
||||||
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
|
<AdminStackNav.Screen name="MapScreen" component={MapScreen}/>
|
||||||
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
<AdminStackNav.Screen name="LeaderboardScreen" component={LeaderboardScreen}/>
|
||||||
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
|
<AdminStackNav.Screen name="PlayersScreen" component={PlayersScreen}/>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ const Screen = ({ children, style, showBack = false }) => {
|
|||||||
TeamScreen: "Current Team",
|
TeamScreen: "Current Team",
|
||||||
MapScreen: "Map",
|
MapScreen: "Map",
|
||||||
GameSettingsScreen: "Game Settings",
|
GameSettingsScreen: "Game Settings",
|
||||||
|
DepartmentSettingsScreen: "Department Settings",
|
||||||
PlayersScreen: "Players",
|
PlayersScreen: "Players",
|
||||||
ExpandedMapScreen: "Map View",
|
ExpandedMapScreen: "Map View",
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,225 @@
|
|||||||
|
import React, {useEffect, useState} from 'react';
|
||||||
|
import {ActivityIndicator, Alert, ScrollView, StyleSheet, Switch, Text, TextInput, View} from 'react-native';
|
||||||
|
import Screen from '../layout/Screen';
|
||||||
|
import {Button, ButtonTray} from '../UI/Button';
|
||||||
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
|
|
||||||
|
const DepartmentSettingsScreen = ({route, navigation}) => {
|
||||||
|
// Initialisation ------------
|
||||||
|
|
||||||
|
const {SGid, Gid} = route.params;
|
||||||
|
const {getSubgroup, updateSubgroup, getCaches, resetGame} = useGameHook();
|
||||||
|
|
||||||
|
// State ----------------------
|
||||||
|
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [deptName, setDeptName] = useState('');
|
||||||
|
const [joinCode, setJoinCode] = useState('');
|
||||||
|
const [teamsEnabled, setTeamsEnabled] = useState(false);
|
||||||
|
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
|
||||||
|
const [cacheCount, setCacheCount] = useState(0);
|
||||||
|
|
||||||
|
// Handlers -------------------
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const load = async () => {
|
||||||
|
const sg = await getSubgroup(SGid);
|
||||||
|
if (sg) {
|
||||||
|
setDeptName(sg.SubGroupName || '');
|
||||||
|
setJoinCode(sg.JoinCode || '');
|
||||||
|
setTeamsEnabled(Boolean(sg.TeamsEnabled));
|
||||||
|
setCacheTriggerMeters(String(sg.CacheTriggerMeters || 20));
|
||||||
|
}
|
||||||
|
// Load cache count for this department
|
||||||
|
const caches = await getCaches(Gid, SGid);
|
||||||
|
setCacheCount((caches || []).length);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
load();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setSaving(true);
|
||||||
|
await updateSubgroup(SGid, {
|
||||||
|
SubGroupName: deptName.trim(),
|
||||||
|
TeamsEnabled: teamsEnabled,
|
||||||
|
CacheTriggerMeters: parseInt(cacheTriggerMeters) || 20,
|
||||||
|
});
|
||||||
|
setSaving(false);
|
||||||
|
Alert.alert('Saved', 'Department settings updated.');
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleResetDepartment = () => {
|
||||||
|
Alert.alert(
|
||||||
|
'Reset Department',
|
||||||
|
'This will reset all cache claims for this department. Are you sure?',
|
||||||
|
[
|
||||||
|
{text: 'Cancel', style: 'cancel'},
|
||||||
|
{
|
||||||
|
text: 'Reset',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
await resetGame(Gid);
|
||||||
|
Alert.alert('Done', 'Department progress has been reset.');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleManageCaches = () => {
|
||||||
|
navigation.navigate('MapScreen');
|
||||||
|
};
|
||||||
|
|
||||||
|
// View -----------------------
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<Screen showBack={true} style={styles.center}>
|
||||||
|
<ActivityIndicator size="large"/>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Screen showBack={true}>
|
||||||
|
<ScrollView contentContainerStyle={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
<Text style={styles.sectionTitle}>Department Settings</Text>
|
||||||
|
|
||||||
|
<Text style={styles.label}>Department Name</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={deptName}
|
||||||
|
onChangeText={setDeptName}
|
||||||
|
placeholder="Enter department name"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.codeSection}>
|
||||||
|
<Text style={styles.codeLabel}>Department Join Code</Text>
|
||||||
|
<Text style={styles.codeValue}>{joinCode || '—'}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.toggleRow}>
|
||||||
|
<Text style={styles.toggleLabel}>Teams Enabled</Text>
|
||||||
|
<Switch
|
||||||
|
value={teamsEnabled}
|
||||||
|
onValueChange={setTeamsEnabled}
|
||||||
|
trackColor={{false: '#d1d5db', true: '#93c5fd'}}
|
||||||
|
thumbColor={teamsEnabled ? '#2563eb' : '#9ca3af'}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<Text style={styles.label}>Cache Claim Distance (metres)</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={cacheTriggerMeters}
|
||||||
|
onChangeText={setCacheTriggerMeters}
|
||||||
|
keyboardType="numeric"
|
||||||
|
placeholder="20"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<View style={styles.infoRow}>
|
||||||
|
<Text style={styles.infoLabel}>Total Caches</Text>
|
||||||
|
<Text style={styles.infoValue}>{cacheCount}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.saveWrap}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label={saving ? 'Saving...' : 'Save Settings'}
|
||||||
|
onClick={handleSave}
|
||||||
|
styleButton={styles.saveButton}
|
||||||
|
styleLabel={styles.saveLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.mapWrap}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label="Manage Caches"
|
||||||
|
onClick={handleManageCaches}
|
||||||
|
styleButton={styles.mapButton}
|
||||||
|
styleLabel={styles.mapLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.resetWrap}>
|
||||||
|
<Button
|
||||||
|
label="Reset Department"
|
||||||
|
onClick={handleResetDepartment}
|
||||||
|
styleButton={styles.resetButton}
|
||||||
|
styleLabel={styles.resetLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
center: {justifyContent: 'center', alignItems: 'center'},
|
||||||
|
formContainer: {paddingBottom: 20},
|
||||||
|
sectionTitle: {fontSize: 20, fontWeight: '700', color: '#1f2937', marginBottom: 16},
|
||||||
|
label: {fontSize: 14, fontWeight: '600', color: '#374151', marginBottom: 6, marginTop: 14},
|
||||||
|
input: {
|
||||||
|
borderWidth: 1,
|
||||||
|
borderColor: '#d1d5db',
|
||||||
|
borderRadius: 8,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 10,
|
||||||
|
fontSize: 16,
|
||||||
|
color: '#1f2937',
|
||||||
|
backgroundColor: '#ffffff',
|
||||||
|
},
|
||||||
|
codeSection: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
codeLabel: {fontSize: 14, fontWeight: '600', color: '#374151'},
|
||||||
|
codeValue: {fontSize: 16, fontWeight: '700', color: '#2563eb', letterSpacing: 2},
|
||||||
|
toggleRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 16,
|
||||||
|
paddingVertical: 10,
|
||||||
|
borderBottomWidth: 1,
|
||||||
|
borderBottomColor: '#e5e7eb',
|
||||||
|
},
|
||||||
|
toggleLabel: {fontSize: 14, fontWeight: '600', color: '#374151'},
|
||||||
|
infoRow: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 16,
|
||||||
|
paddingVertical: 12,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
backgroundColor: '#f3f4f6',
|
||||||
|
borderRadius: 8,
|
||||||
|
},
|
||||||
|
infoLabel: {fontSize: 14, fontWeight: '600', color: '#374151'},
|
||||||
|
infoValue: {fontSize: 16, fontWeight: '700', color: '#1f2937'},
|
||||||
|
saveWrap: {marginTop: 24},
|
||||||
|
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
||||||
|
saveLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
mapWrap: {marginTop: 16},
|
||||||
|
mapButton: {backgroundColor: '#2563eb', borderColor: '#2563eb'},
|
||||||
|
mapLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
resetWrap: {marginTop: 30},
|
||||||
|
resetButton: {backgroundColor: '#dc2626', borderColor: '#dc2626'},
|
||||||
|
resetLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default DepartmentSettingsScreen;
|
||||||
|
|
||||||
@@ -6,11 +6,11 @@ import {Button, ButtonTray} from '../UI/Button';
|
|||||||
import useGameHook from '../../hooks/useGameHook';
|
import useGameHook from '../../hooks/useGameHook';
|
||||||
import {getSession, setSessionTeamsEnabled} from '../../hooks/SessionStore';
|
import {getSession, setSessionTeamsEnabled} from '../../hooks/SessionStore';
|
||||||
|
|
||||||
const GameSettingsScreen = () => {
|
const GameSettingsScreen = ({navigation}) => {
|
||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const {getLobby, updateGroup, getSubgroups, createSubgroup, updateSubgroup, deleteSubgroup, resetGame, getAdminWaitlist, approveAdmin, rejectAdmin, getUser} = useGameHook();
|
const {getLobby, updateGroup, getSubgroups, createSubgroup, updateSubgroup, deleteSubgroup, resetGame, getAdminWaitlist, approveAdmin, rejectAdmin, getUser, disbandTeams} = useGameHook();
|
||||||
const isBusiness = Boolean(session.isBusiness);
|
const isBusiness = Boolean(session.isBusiness);
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
@@ -18,17 +18,18 @@ const GameSettingsScreen = () => {
|
|||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [saving, setSaving] = useState(false);
|
const [saving, setSaving] = useState(false);
|
||||||
const [groupName, setGroupName] = useState('');
|
const [groupName, setGroupName] = useState('');
|
||||||
|
const [businessName, setBusinessName] = useState('');
|
||||||
const [teamsEnabled, setTeamsEnabled] = useState(session.teamsEnabled);
|
const [teamsEnabled, setTeamsEnabled] = useState(session.teamsEnabled);
|
||||||
|
const [serverTeamsEnabled, setServerTeamsEnabled] = useState(session.teamsEnabled);
|
||||||
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
|
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
|
||||||
const [adminJoinCode, setAdminJoinCode] = useState('');
|
const [adminJoinCode, setAdminJoinCode] = useState('');
|
||||||
|
const [orgJoinCode, setOrgJoinCode] = useState('');
|
||||||
const [memberJoinCode, setMemberJoinCode] = useState('');
|
const [memberJoinCode, setMemberJoinCode] = useState('');
|
||||||
const [waitlist, setWaitlist] = useState([]);
|
const [waitlist, setWaitlist] = useState([]);
|
||||||
const [waitlistNames, setWaitlistNames] = useState({});
|
const [waitlistNames, setWaitlistNames] = useState({});
|
||||||
// Business subgroup management
|
// Business subgroup management
|
||||||
const [subgroupList, setSubgroupList] = useState([]);
|
const [subgroupList, setSubgroupList] = useState([]);
|
||||||
const [newDeptName, setNewDeptName] = useState('');
|
const [newDeptName, setNewDeptName] = useState('');
|
||||||
const [editingSGid, setEditingSGid] = useState(null);
|
|
||||||
const [editDeptTrigger, setEditDeptTrigger] = useState('20');
|
|
||||||
|
|
||||||
// Handlers -------------------
|
// Handlers -------------------
|
||||||
|
|
||||||
@@ -62,12 +63,14 @@ const GameSettingsScreen = () => {
|
|||||||
const group = await getLobby(session.currentGid);
|
const group = await getLobby(session.currentGid);
|
||||||
if (group) {
|
if (group) {
|
||||||
setGroupName(group.GroupName || '');
|
setGroupName(group.GroupName || '');
|
||||||
|
setBusinessName(group.BusinessOrSchoolName || '');
|
||||||
const serverTeams = Boolean(group.TeamsEnabled);
|
const serverTeams = Boolean(group.TeamsEnabled);
|
||||||
setTeamsEnabled(serverTeams);
|
setTeamsEnabled(serverTeams);
|
||||||
// Keep session in sync so the toggle survives app reloads
|
setServerTeamsEnabled(serverTeams);
|
||||||
setSessionTeamsEnabled(serverTeams);
|
setSessionTeamsEnabled(serverTeams);
|
||||||
setCacheTriggerMeters(String(group.CacheTriggerMeters || 20));
|
setCacheTriggerMeters(String(group.CacheTriggerMeters || 20));
|
||||||
setAdminJoinCode(group.AdminJoinCode || '');
|
setAdminJoinCode(group.AdminJoinCode || '');
|
||||||
|
setOrgJoinCode(group.OrgJoinCode || '');
|
||||||
}
|
}
|
||||||
await loadSubgroups();
|
await loadSubgroups();
|
||||||
await loadWaitlist();
|
await loadWaitlist();
|
||||||
@@ -76,19 +79,51 @@ const GameSettingsScreen = () => {
|
|||||||
load();
|
load();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSave = async () => {
|
// --- Individual save ---
|
||||||
if (!session.currentGid) return;
|
const saveSettings = async () => {
|
||||||
setSaving(true);
|
setSaving(true);
|
||||||
const result = await updateGroup(session.currentGid, {
|
const result = await updateGroup(session.currentGid, {
|
||||||
GroupName: groupName.trim(),
|
GroupName: groupName.trim(),
|
||||||
TeamsEnabled: teamsEnabled,
|
TeamsEnabled: teamsEnabled,
|
||||||
CacheTriggerMeters: parseInt(cacheTriggerMeters) || 20,
|
CacheTriggerMeters: parseInt(cacheTriggerMeters) || 20,
|
||||||
});
|
});
|
||||||
// Persist the new toggle value so the session reflects it after a reload
|
if (result) {
|
||||||
if (result) setSessionTeamsEnabled(teamsEnabled);
|
if (serverTeamsEnabled && !teamsEnabled) {
|
||||||
|
await disbandTeams(session.currentGid);
|
||||||
|
}
|
||||||
|
setSessionTeamsEnabled(teamsEnabled);
|
||||||
|
setServerTeamsEnabled(teamsEnabled);
|
||||||
|
}
|
||||||
setSaving(false);
|
setSaving(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
if (!session.currentGid) return;
|
||||||
|
// Business save — only save business name
|
||||||
|
if (isBusiness) {
|
||||||
|
setSaving(true);
|
||||||
|
await updateGroup(session.currentGid, {
|
||||||
|
BusinessOrSchoolName: businessName.trim(),
|
||||||
|
GroupName: businessName.trim(),
|
||||||
|
});
|
||||||
|
setSaving(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Individual save — check teams toggle
|
||||||
|
if (serverTeamsEnabled && !teamsEnabled) {
|
||||||
|
Alert.alert(
|
||||||
|
'Disable Teams',
|
||||||
|
'This will disband all existing teams and remove all players from their teams. Continue?',
|
||||||
|
[
|
||||||
|
{text: 'Cancel', style: 'cancel'},
|
||||||
|
{text: 'Disable', style: 'destructive', onPress: saveSettings},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await saveSettings();
|
||||||
|
};
|
||||||
|
|
||||||
const handleResetGame = () => {
|
const handleResetGame = () => {
|
||||||
Alert.alert('Reset Entire Game', 'This will remove ALL saved caches and reset progress for every player. Are you sure?', [
|
Alert.alert('Reset Entire Game', 'This will remove ALL saved caches and reset progress for every player. Are you sure?', [
|
||||||
{text: 'Cancel', style: 'cancel'},
|
{text: 'Cancel', style: 'cancel'},
|
||||||
@@ -115,24 +150,19 @@ const GameSettingsScreen = () => {
|
|||||||
|
|
||||||
// Business — create a new department (subgroup)
|
// Business — create a new department (subgroup)
|
||||||
const handleCreateDepartment = async () => {
|
const handleCreateDepartment = async () => {
|
||||||
if (!newDeptName.trim() || !session.currentGid) return;
|
const deptCount = subgroupList.filter((sg) => !sg.IsAdminGroup).length;
|
||||||
|
const name = newDeptName.trim() || `Department ${deptCount + 1}`;
|
||||||
|
if (!session.currentGid) return;
|
||||||
await createSubgroup({
|
await createSubgroup({
|
||||||
SubGroupName: newDeptName.trim(),
|
SubGroupName: name,
|
||||||
Gid: session.currentGid,
|
Gid: session.currentGid,
|
||||||
IsAdminGroup: false,
|
IsAdminGroup: false,
|
||||||
CacheTriggerMeters: parseInt(cacheTriggerMeters) || 20,
|
CacheTriggerMeters: 20,
|
||||||
});
|
});
|
||||||
setNewDeptName('');
|
setNewDeptName('');
|
||||||
await loadSubgroups();
|
await loadSubgroups();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Business — save per-subgroup trigger distance
|
|
||||||
const handleSaveDeptTrigger = async (sgid) => {
|
|
||||||
await updateSubgroup(sgid, {CacheTriggerMeters: parseInt(editDeptTrigger) || 20});
|
|
||||||
setEditingSGid(null);
|
|
||||||
await loadSubgroups();
|
|
||||||
};
|
|
||||||
|
|
||||||
// Business — delete a department
|
// Business — delete a department
|
||||||
const handleDeleteDepartment = (sg) => {
|
const handleDeleteDepartment = (sg) => {
|
||||||
Alert.alert('Delete Department', `Delete "${sg.SubGroupName}" and all its data?`, [
|
Alert.alert('Delete Department', `Delete "${sg.SubGroupName}" and all its data?`, [
|
||||||
@@ -148,6 +178,11 @@ const GameSettingsScreen = () => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Business — navigate to department settings
|
||||||
|
const handleEnterDepartment = (sg) => {
|
||||||
|
navigation.navigate('DepartmentSettingsScreen', {SGid: sg.SGid, Gid: session.currentGid});
|
||||||
|
};
|
||||||
|
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -169,6 +204,135 @@ const GameSettingsScreen = () => {
|
|||||||
// Filter subgroups for the department list (non-admin only)
|
// Filter subgroups for the department list (non-admin only)
|
||||||
const departments = subgroupList.filter((sg) => !sg.IsAdminGroup);
|
const departments = subgroupList.filter((sg) => !sg.IsAdminGroup);
|
||||||
|
|
||||||
|
// ===== Business Admin View =====
|
||||||
|
if (isBusiness) {
|
||||||
|
return (
|
||||||
|
<Screen>
|
||||||
|
<ScrollView contentContainerStyle={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||||
|
<Text style={styles.sectionTitle}>Business Settings</Text>
|
||||||
|
|
||||||
|
<Text style={styles.label}>Business Name</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.input}
|
||||||
|
value={businessName}
|
||||||
|
onChangeText={setBusinessName}
|
||||||
|
placeholder="Enter business name"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<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}>Organisation Join Code</Text>
|
||||||
|
<Text style={styles.codeValue}>{orgJoinCode || '—'}</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.saveWrap}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label={saving ? 'Saving...' : 'Save Settings'}
|
||||||
|
onClick={handleSave}
|
||||||
|
styleButton={styles.saveButton}
|
||||||
|
styleLabel={styles.saveLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Departments List */}
|
||||||
|
<View style={styles.deptSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Departments</Text>
|
||||||
|
{departments.map((sg) => (
|
||||||
|
<Card key={sg.SGid}>
|
||||||
|
<View style={styles.deptRow}>
|
||||||
|
<View style={styles.deptInfo}>
|
||||||
|
<Text style={styles.deptName}>{sg.SubGroupName}</Text>
|
||||||
|
<Text style={styles.deptCode}>Join Code: {sg.JoinCode || '—'}</Text>
|
||||||
|
</View>
|
||||||
|
<View style={styles.deptActions}>
|
||||||
|
<Button
|
||||||
|
label="Delete"
|
||||||
|
onClick={() => handleDeleteDepartment(sg)}
|
||||||
|
styleButton={styles.deptDeleteButton}
|
||||||
|
styleLabel={styles.deptBtnLabel}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="→"
|
||||||
|
onClick={() => handleEnterDepartment(sg)}
|
||||||
|
styleButton={styles.deptArrowButton}
|
||||||
|
styleLabel={styles.deptArrowLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
{departments.length === 0 && (
|
||||||
|
<Text style={styles.emptyText}>No departments yet.</Text>
|
||||||
|
)}
|
||||||
|
<View style={styles.createDeptRow}>
|
||||||
|
<TextInput
|
||||||
|
style={[styles.input, {flex: 1}]}
|
||||||
|
value={newDeptName}
|
||||||
|
onChangeText={setNewDeptName}
|
||||||
|
placeholder="New department name"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Add"
|
||||||
|
onClick={handleCreateDepartment}
|
||||||
|
styleButton={styles.createDeptButton}
|
||||||
|
styleLabel={styles.createDeptLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
{/* Admin Waitlist */}
|
||||||
|
{waitlist.length > 0 && (
|
||||||
|
<View style={styles.waitlistSection}>
|
||||||
|
<Text style={styles.sectionTitle}>Admin Waitlist</Text>
|
||||||
|
{waitlist.map((entry) => (
|
||||||
|
<Card key={entry.id}>
|
||||||
|
<View style={styles.waitlistRow}>
|
||||||
|
<Text style={styles.waitlistName}>
|
||||||
|
{waitlistNames[entry.Uid] || `User ${entry.Uid}`}
|
||||||
|
</Text>
|
||||||
|
<View style={styles.waitlistActions}>
|
||||||
|
<Button
|
||||||
|
label="Approve"
|
||||||
|
onClick={() => handleApproveAdmin(entry)}
|
||||||
|
styleButton={styles.approveButton}
|
||||||
|
styleLabel={styles.approveBtnLabel}
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Reject"
|
||||||
|
onClick={() => handleRejectAdmin(entry)}
|
||||||
|
styleButton={styles.rejectButton}
|
||||||
|
styleLabel={styles.rejectBtnLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</Card>
|
||||||
|
))}
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Reset Entire Game */}
|
||||||
|
<View style={styles.resetSection}>
|
||||||
|
<Button
|
||||||
|
label="Reset Entire Game"
|
||||||
|
onClick={handleResetGame}
|
||||||
|
styleButton={styles.resetButton}
|
||||||
|
styleLabel={styles.resetLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</ScrollView>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Individual Admin View =====
|
||||||
return (
|
return (
|
||||||
<Screen>
|
<Screen>
|
||||||
<ScrollView contentContainerStyle={styles.formContainer} showsVerticalScrollIndicator={false}>
|
<ScrollView contentContainerStyle={styles.formContainer} showsVerticalScrollIndicator={false}>
|
||||||
@@ -224,83 +388,6 @@ const GameSettingsScreen = () => {
|
|||||||
</ButtonTray>
|
</ButtonTray>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
{/* Business: Departments / Subgroups management */}
|
|
||||||
{isBusiness && (
|
|
||||||
<View style={styles.deptSection}>
|
|
||||||
<Text style={styles.sectionTitle}>Departments</Text>
|
|
||||||
{departments.map((sg) => (
|
|
||||||
<Card key={sg.SGid}>
|
|
||||||
<View style={styles.deptRow}>
|
|
||||||
<View style={styles.deptInfo}>
|
|
||||||
<Text style={styles.deptName}>{sg.SubGroupName}</Text>
|
|
||||||
<Text style={styles.deptCode}>Join Code: {sg.JoinCode || '—'}</Text>
|
|
||||||
<Text style={styles.deptTrigger}>Claim Distance: {sg.CacheTriggerMeters || 20}m</Text>
|
|
||||||
</View>
|
|
||||||
<View style={styles.deptActions}>
|
|
||||||
<Button
|
|
||||||
label="Edit"
|
|
||||||
onClick={() => { setEditingSGid(sg.SGid); setEditDeptTrigger(String(sg.CacheTriggerMeters || 20)); }}
|
|
||||||
styleButton={styles.deptEditButton}
|
|
||||||
styleLabel={styles.deptBtnLabel}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
label="Delete"
|
|
||||||
onClick={() => handleDeleteDepartment(sg)}
|
|
||||||
styleButton={styles.deptDeleteButton}
|
|
||||||
styleLabel={styles.deptBtnLabel}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{editingSGid === sg.SGid && (
|
|
||||||
<View style={styles.deptEditRow}>
|
|
||||||
<Text style={styles.label}>Claim Distance (metres)</Text>
|
|
||||||
<TextInput
|
|
||||||
style={styles.input}
|
|
||||||
value={editDeptTrigger}
|
|
||||||
onChangeText={setEditDeptTrigger}
|
|
||||||
keyboardType="numeric"
|
|
||||||
placeholder="20"
|
|
||||||
placeholderTextColor="#9ca3af"
|
|
||||||
/>
|
|
||||||
<ButtonTray>
|
|
||||||
<Button
|
|
||||||
label="Save"
|
|
||||||
onClick={() => handleSaveDeptTrigger(sg.SGid)}
|
|
||||||
styleButton={styles.deptSaveButton}
|
|
||||||
styleLabel={styles.deptBtnLabel}
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
label="Cancel"
|
|
||||||
onClick={() => setEditingSGid(null)}
|
|
||||||
styleButton={styles.deptCancelButton}
|
|
||||||
styleLabel={styles.deptBtnLabel}
|
|
||||||
/>
|
|
||||||
</ButtonTray>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
</Card>
|
|
||||||
))}
|
|
||||||
{departments.length === 0 && (
|
|
||||||
<Text style={styles.emptyText}>No departments yet.</Text>
|
|
||||||
)}
|
|
||||||
<View style={styles.createDeptRow}>
|
|
||||||
<TextInput
|
|
||||||
style={[styles.input, {flex: 1}]}
|
|
||||||
value={newDeptName}
|
|
||||||
onChangeText={setNewDeptName}
|
|
||||||
placeholder="New department name"
|
|
||||||
placeholderTextColor="#9ca3af"
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
label="Add"
|
|
||||||
onClick={handleCreateDepartment}
|
|
||||||
styleButton={styles.createDeptButton}
|
|
||||||
styleLabel={styles.createDeptLabel}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Admin Waitlist */}
|
{/* Admin Waitlist */}
|
||||||
{waitlist.length > 0 && (
|
{waitlist.length > 0 && (
|
||||||
<View style={styles.waitlistSection}>
|
<View style={styles.waitlistSection}>
|
||||||
@@ -388,18 +475,15 @@ const styles = StyleSheet.create({
|
|||||||
saveLabel: {color: '#ffffff', fontWeight: '600'},
|
saveLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
// Department / subgroup styles
|
// Department / subgroup styles
|
||||||
deptSection: {marginTop: 30},
|
deptSection: {marginTop: 30},
|
||||||
deptRow: {flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start'},
|
deptRow: {flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center'},
|
||||||
deptInfo: {flex: 1},
|
deptInfo: {flex: 1},
|
||||||
deptName: {fontSize: 15, fontWeight: '700', color: '#1f2937'},
|
deptName: {fontSize: 15, fontWeight: '700', color: '#1f2937'},
|
||||||
deptCode: {fontSize: 13, fontWeight: '600', color: '#2563eb', marginTop: 2, letterSpacing: 1},
|
deptCode: {fontSize: 13, fontWeight: '600', color: '#2563eb', marginTop: 2, letterSpacing: 1},
|
||||||
deptTrigger: {fontSize: 12, color: '#6b7280', marginTop: 2},
|
|
||||||
deptActions: {flexDirection: 'row', gap: 6},
|
deptActions: {flexDirection: 'row', gap: 6},
|
||||||
deptEditButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', minHeight: 36, flex: 0, paddingHorizontal: 10},
|
|
||||||
deptDeleteButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 10},
|
deptDeleteButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 10},
|
||||||
deptBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
|
deptBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
|
||||||
deptEditRow: {marginTop: 10, paddingTop: 10, borderTopWidth: 1, borderTopColor: '#e5e7eb'},
|
deptArrowButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', minHeight: 36, flex: 0, paddingHorizontal: 14},
|
||||||
deptSaveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
deptArrowLabel: {color: '#ffffff', fontWeight: '700', fontSize: 18},
|
||||||
deptCancelButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'},
|
|
||||||
createDeptRow: {flexDirection: 'row', gap: 10, marginTop: 12, alignItems: 'center'},
|
createDeptRow: {flexDirection: 'row', gap: 10, marginTop: 12, alignItems: 'center'},
|
||||||
createDeptButton: {backgroundColor: '#16a34a', borderColor: '#16a34a', flex: 0, paddingHorizontal: 16},
|
createDeptButton: {backgroundColor: '#16a34a', borderColor: '#16a34a', flex: 0, paddingHorizontal: 16},
|
||||||
createDeptLabel: {color: '#ffffff', fontWeight: '600'},
|
createDeptLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const LeaderboardScreen = () => {
|
|||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const isAdmin = session.isAcceptedAdmin;
|
const isAdmin = session.isAcceptedAdmin;
|
||||||
const {getLobby, getTeams, getTeamMembers, getGroupMembers, getCaches, getUser, deleteTeam, resetPlayerProgress} = useGameHook();
|
const {getLobby, getTeams, getTeamMembers, getGroupMembers, getCaches, getUser, deleteTeam, resetPlayerProgress, resetTeamProgress} = useGameHook();
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -133,6 +133,21 @@ const LeaderboardScreen = () => {
|
|||||||
]);
|
]);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleResetTeam = (team) => {
|
||||||
|
Alert.alert('Reset Team Progress', `Reset all cache claims for team "${team.name}"?`, [
|
||||||
|
{text: 'Cancel', style: 'cancel'},
|
||||||
|
{
|
||||||
|
text: 'Reset',
|
||||||
|
style: 'destructive',
|
||||||
|
onPress: async () => {
|
||||||
|
await resetTeamProgress(session.currentGid, team.tid);
|
||||||
|
Alert.alert('Done', `Progress for team "${team.name}" has been reset.`);
|
||||||
|
await loadData();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
@@ -179,6 +194,12 @@ const LeaderboardScreen = () => {
|
|||||||
</View>
|
</View>
|
||||||
))}
|
))}
|
||||||
<View style={styles.deleteTeamWrap}>
|
<View style={styles.deleteTeamWrap}>
|
||||||
|
<Button
|
||||||
|
label="Reset Team Progress"
|
||||||
|
onClick={() => handleResetTeam(team)}
|
||||||
|
styleButton={styles.resetTeamButton}
|
||||||
|
styleLabel={styles.deleteTeamLabel}
|
||||||
|
/>
|
||||||
<Button
|
<Button
|
||||||
label="Delete Team"
|
label="Delete Team"
|
||||||
onClick={() => handleDeleteTeam(team)}
|
onClick={() => handleDeleteTeam(team)}
|
||||||
@@ -377,8 +398,9 @@ const styles = StyleSheet.create({
|
|||||||
subScore: {fontSize: 13, fontWeight: '600', color: '#16a34a', marginRight: 8},
|
subScore: {fontSize: 13, fontWeight: '600', color: '#16a34a', marginRight: 8},
|
||||||
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14},
|
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14},
|
||||||
// Admin delete team button
|
// Admin delete team button
|
||||||
deleteTeamWrap: {marginTop: 8, paddingLeft: 42},
|
deleteTeamWrap: {marginTop: 8, paddingLeft: 42, flexDirection: 'row', gap: 8},
|
||||||
deleteTeamButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36},
|
resetTeamButton: {backgroundColor: '#f59e0b', borderColor: '#f59e0b', minHeight: 36, flex: 1},
|
||||||
|
deleteTeamButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 1},
|
||||||
deleteTeamLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
|
deleteTeamLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
|
||||||
// Admin reset mini button (inline)
|
// Admin reset mini button (inline)
|
||||||
resetMiniButton: {
|
resetMiniButton: {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ const MapScreen = ({navigation}) => {
|
|||||||
// Initialisation ------------
|
// Initialisation ------------
|
||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, createPrivateGame, getLobby, getUser, getSubgroups} = useGameHook();
|
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, createPrivateGame, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook();
|
||||||
|
|
||||||
// Stable ref to prevent infinite re-render loops
|
// Stable ref to prevent infinite re-render loops
|
||||||
const getCachesRef = useRef(getCaches);
|
const getCachesRef = useRef(getCaches);
|
||||||
@@ -41,6 +41,13 @@ const MapScreen = ({navigation}) => {
|
|||||||
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
|
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
|
||||||
const [subgroups, setSubgroups] = useState([]);
|
const [subgroups, setSubgroups] = useState([]);
|
||||||
|
|
||||||
|
// Business join flow state
|
||||||
|
const [joinStep, setJoinStep] = useState(null); // null | 'orgCode' | 'deptCode'
|
||||||
|
const [orgCode, setOrgCode] = useState('');
|
||||||
|
const [deptCode, setDeptCode] = useState('');
|
||||||
|
const [matchedGroup, setMatchedGroup] = useState(null);
|
||||||
|
const [joinError, setJoinError] = useState('');
|
||||||
|
|
||||||
// Admin cache creation / editing state
|
// Admin cache creation / editing state
|
||||||
const [isCreating, setIsCreating] = useState(false);
|
const [isCreating, setIsCreating] = useState(false);
|
||||||
const [editingCacheId, setEditingCacheId] = useState(null);
|
const [editingCacheId, setEditingCacheId] = useState(null);
|
||||||
@@ -190,6 +197,60 @@ const MapScreen = ({navigation}) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Business — create a new company
|
||||||
|
const handleCreateCompany = async () => {
|
||||||
|
const result = await createPrivateGame({
|
||||||
|
GroupName: 'My Company',
|
||||||
|
BusinessOrSchoolName: 'My Company',
|
||||||
|
CreatedByUid: session.currentUid,
|
||||||
|
TeamsEnabled: false,
|
||||||
|
MaxMemberSubgroups: 1,
|
||||||
|
isBusiness: true,
|
||||||
|
});
|
||||||
|
if (!result) return;
|
||||||
|
const freshUser = await getUser(session.currentUid);
|
||||||
|
if (freshUser) {
|
||||||
|
setSessionUser(freshUser);
|
||||||
|
setInGame(true);
|
||||||
|
setIsAdmin(true);
|
||||||
|
navigation.reset({index: 0, routes: [{name: 'Admin'}]});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Business — verify the org code
|
||||||
|
const handleVerifyOrgCode = async () => {
|
||||||
|
setJoinError('');
|
||||||
|
if (!orgCode.trim()) return;
|
||||||
|
const group = await getGroupByOrgCode(orgCode.trim().toUpperCase());
|
||||||
|
if (!group) {
|
||||||
|
setJoinError('Organisation not found. Check your code and try again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setMatchedGroup(group);
|
||||||
|
setJoinStep('deptCode');
|
||||||
|
};
|
||||||
|
|
||||||
|
// Business — join a department within the matched org
|
||||||
|
const handleJoinDepartment = async () => {
|
||||||
|
setJoinError('');
|
||||||
|
if (!deptCode.trim()) return;
|
||||||
|
const result = await joinPrivateGame({JoinCode: deptCode.trim().toUpperCase(), Uid: session.currentUid});
|
||||||
|
if (!result) {
|
||||||
|
setJoinError('Invalid department code. Check the code and try again.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setSessionGroup(result.Gid, result.SGid);
|
||||||
|
const freshUser = await getUser(session.currentUid);
|
||||||
|
if (freshUser) {
|
||||||
|
setSessionUser(freshUser);
|
||||||
|
setSessionTeam(freshUser.TGid ?? null);
|
||||||
|
}
|
||||||
|
setInGame(true);
|
||||||
|
setIsAdmin(false);
|
||||||
|
setJoinStep(null);
|
||||||
|
navigation.navigate('TeamScreen');
|
||||||
|
};
|
||||||
|
|
||||||
const handleClaim = useCallback(async (cacheId) => {
|
const handleClaim = useCallback(async (cacheId) => {
|
||||||
if (!session.currentGid) return;
|
if (!session.currentGid) return;
|
||||||
const result = await claimCache({
|
const result = await claimCache({
|
||||||
@@ -275,7 +336,112 @@ const MapScreen = ({navigation}) => {
|
|||||||
|
|
||||||
// View -----------------------
|
// View -----------------------
|
||||||
|
|
||||||
// Not in a game
|
// Not in a game — Business flow
|
||||||
|
if (!inGame && session.isBusiness) {
|
||||||
|
// Step 2: Enter department code
|
||||||
|
if (joinStep === 'deptCode' && matchedGroup) {
|
||||||
|
return (
|
||||||
|
<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"
|
||||||
|
value={deptCode}
|
||||||
|
onChangeText={(text) => setDeptCode(text.toUpperCase())}
|
||||||
|
autoCapitalize="characters"
|
||||||
|
/>
|
||||||
|
<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>
|
||||||
|
<Button
|
||||||
|
label="Back"
|
||||||
|
onClick={() => { setJoinStep('orgCode'); setDeptCode(''); setJoinError(''); }}
|
||||||
|
styleButton={styles.backButton}
|
||||||
|
styleLabel={styles.backLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 1: Enter org code (or choose create / join)
|
||||||
|
if (joinStep === 'orgCode') {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<Text style={styles.bizTitle}>Join a Company</Text>
|
||||||
|
<Text style={styles.bizSubtitle}>Enter the organisation code provided by your company</Text>
|
||||||
|
<View style={styles.inputRow}>
|
||||||
|
<TextInput
|
||||||
|
style={styles.codeInput}
|
||||||
|
placeholder="Organisation Code"
|
||||||
|
placeholderTextColor="#9ca3af"
|
||||||
|
value={orgCode}
|
||||||
|
onChangeText={(text) => setOrgCode(text.toUpperCase())}
|
||||||
|
autoCapitalize="characters"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
label="Next"
|
||||||
|
onClick={handleVerifyOrgCode}
|
||||||
|
styleButton={styles.joinButton}
|
||||||
|
styleLabel={styles.joinLabel}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
{joinError ? <Text style={styles.joinError}>{joinError}</Text> : null}
|
||||||
|
<View style={styles.fullRow}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label="Back"
|
||||||
|
onClick={() => { setJoinStep(null); setOrgCode(''); setJoinError(''); }}
|
||||||
|
styleButton={styles.backButton}
|
||||||
|
styleLabel={styles.backLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default: Create company or Join company choice
|
||||||
|
return (
|
||||||
|
<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.fullRow}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label="Create Company"
|
||||||
|
onClick={handleCreateCompany}
|
||||||
|
styleButton={styles.createButton}
|
||||||
|
styleLabel={styles.createLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
<View style={styles.fullRow}>
|
||||||
|
<ButtonTray>
|
||||||
|
<Button
|
||||||
|
label="Join Company"
|
||||||
|
onClick={() => setJoinStep('orgCode')}
|
||||||
|
styleButton={styles.joinButton}
|
||||||
|
styleLabel={styles.joinLabel}
|
||||||
|
/>
|
||||||
|
</ButtonTray>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not in a game — Individual flow
|
||||||
if (!inGame) {
|
if (!inGame) {
|
||||||
return (
|
return (
|
||||||
<Screen style={styles.center}>
|
<Screen style={styles.center}>
|
||||||
@@ -567,6 +733,12 @@ const styles = StyleSheet.create({
|
|||||||
joinLabel: {color: '#ffffff', fontWeight: '600'},
|
joinLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
createButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
createButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'},
|
||||||
createLabel: {color: '#ffffff', fontWeight: '600'},
|
createLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
|
// Business join flow styles
|
||||||
|
bizTitle: {fontSize: 22, fontWeight: '700', color: '#1f2937', marginBottom: 6, textAlign: 'center'},
|
||||||
|
bizSubtitle: {fontSize: 14, color: '#6b7280', textAlign: 'center', marginBottom: 20, paddingHorizontal: 20},
|
||||||
|
joinError: {color: '#dc2626', textAlign: 'center', marginTop: 8, marginBottom: 4, fontSize: 14, paddingHorizontal: 20},
|
||||||
|
backButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'},
|
||||||
|
backLabel: {color: '#ffffff', fontWeight: '600'},
|
||||||
warning: {
|
warning: {
|
||||||
color: '#dc2626',
|
color: '#dc2626',
|
||||||
fontWeight: 'bold',
|
fontWeight: 'bold',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const TeamScreen = () => {
|
|||||||
|
|
||||||
const session = getSession();
|
const session = getSession();
|
||||||
const isAdmin = session.isAcceptedAdmin;
|
const isAdmin = session.isAcceptedAdmin;
|
||||||
const {getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook();
|
const {getLobby, getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook();
|
||||||
|
|
||||||
// State ----------------------
|
// State ----------------------
|
||||||
|
|
||||||
@@ -21,6 +21,7 @@ const TeamScreen = () => {
|
|||||||
const [members, setMembers] = useState([]);
|
const [members, setMembers] = useState([]);
|
||||||
const [memberNames, setMemberNames] = useState({});
|
const [memberNames, setMemberNames] = useState({});
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [teamsDisabled, setTeamsDisabled] = useState(false);
|
||||||
const [onAdminWaitlist, setOnAdminWaitlist] = useState(false);
|
const [onAdminWaitlist, setOnAdminWaitlist] = useState(false);
|
||||||
const [editingName, setEditingName] = useState(false);
|
const [editingName, setEditingName] = useState(false);
|
||||||
const [teamNameDraft, setTeamNameDraft] = useState('');
|
const [teamNameDraft, setTeamNameDraft] = useState('');
|
||||||
@@ -42,8 +43,16 @@ const TeamScreen = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
// Check if the user is on the admin waitlist
|
if (!session.currentGid) { setLoading(false); return; }
|
||||||
if (session.currentGid && !activeTid) {
|
|
||||||
|
const group = await getLobby(session.currentGid);
|
||||||
|
if (group && !group.TeamsEnabled) {
|
||||||
|
setTeamsDisabled(true);
|
||||||
|
setLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!activeTid) {
|
||||||
const waitlist = await getAdminWaitlist(session.currentGid, session.currentUid);
|
const waitlist = await getAdminWaitlist(session.currentGid, session.currentUid);
|
||||||
if (waitlist.length > 0) {
|
if (waitlist.length > 0) {
|
||||||
setOnAdminWaitlist(true);
|
setOnAdminWaitlist(true);
|
||||||
@@ -126,6 +135,17 @@ const TeamScreen = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (teamsDisabled) {
|
||||||
|
return (
|
||||||
|
<Screen style={styles.center}>
|
||||||
|
<View style={styles.disabledWrap}>
|
||||||
|
<Text style={styles.disabledTitle}>Teams Disabled</Text>
|
||||||
|
<Text style={styles.disabledBody}>The admin has not enabled teams for this game.</Text>
|
||||||
|
</View>
|
||||||
|
</Screen>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// Waiting for admin approval
|
// Waiting for admin approval
|
||||||
if (onAdminWaitlist) {
|
if (onAdminWaitlist) {
|
||||||
return (
|
return (
|
||||||
@@ -270,6 +290,9 @@ 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},
|
||||||
|
disabledWrap: {paddingHorizontal: 30, alignItems: 'center', opacity: 0.45},
|
||||||
|
disabledTitle: {fontSize: 20, fontWeight: '700', color: '#6b7280', marginBottom: 10, textAlign: 'center'},
|
||||||
|
disabledBody: {fontSize: 15, color: '#9ca3af', textAlign: 'center'},
|
||||||
// Shown when the user has not yet joined a game
|
// Shown when the user has not yet joined a game
|
||||||
notInGameText: {color: '#6b7280', fontSize: 15, fontWeight: '600', marginBottom: 16, textAlign: 'center', paddingHorizontal: 20},
|
notInGameText: {color: '#6b7280', fontSize: 15, fontWeight: '600', marginBottom: 16, textAlign: 'center', paddingHorizontal: 20},
|
||||||
inputDisabled: {backgroundColor: '#f3f4f6', opacity: 0.5},
|
inputDisabled: {backgroundColor: '#f3f4f6', opacity: 0.5},
|
||||||
|
|||||||
@@ -41,6 +41,13 @@ const useGameHook = () => {
|
|||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Look up a group by its Organisation Join Code
|
||||||
|
const getGroupByOrgCode = async (code) => {
|
||||||
|
const response = await API.get(`${groupsEndpoint}?OrgJoinCode=${code}`);
|
||||||
|
if (response.isSuccess && response.result.length > 0) return response.result[0];
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
// Subgroups
|
// Subgroups
|
||||||
const getSubgroups = async (gid) => {
|
const getSubgroups = async (gid) => {
|
||||||
const response = await API.get(`${subgroupsEndpoint}?Gid=${gid}`);
|
const response = await API.get(`${subgroupsEndpoint}?Gid=${gid}`);
|
||||||
@@ -109,6 +116,11 @@ const useGameHook = () => {
|
|||||||
return response.isSuccess;
|
return response.isSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const disbandTeams = async (gid) => {
|
||||||
|
const response = await API.post(`${teamsEndpoint}/disband?Gid=${gid}`, {});
|
||||||
|
return response.isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
const joinTeamByCode = async (payload) => {
|
const joinTeamByCode = async (payload) => {
|
||||||
const response = await API.post(teamMembersEndpoint, payload);
|
const response = await API.post(teamMembersEndpoint, payload);
|
||||||
return response.isSuccess ? response.result : null;
|
return response.isSuccess ? response.result : null;
|
||||||
@@ -175,6 +187,11 @@ const useGameHook = () => {
|
|||||||
return response.isSuccess;
|
return response.isSuccess;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const resetTeamProgress = async (gid, tid) => {
|
||||||
|
const response = await API.post(`${cachesEndpoint}/reset-team/${tid}?Gid=${gid}`, {});
|
||||||
|
return response.isSuccess;
|
||||||
|
};
|
||||||
|
|
||||||
// Admin waitlist
|
// Admin waitlist
|
||||||
const getAdminWaitlist = async (gid, uid = null) => {
|
const getAdminWaitlist = async (gid, uid = null) => {
|
||||||
let url = `${adminWaitlistEndpoint}?Gid=${gid}`;
|
let url = `${adminWaitlistEndpoint}?Gid=${gid}`;
|
||||||
@@ -201,6 +218,7 @@ const useGameHook = () => {
|
|||||||
createPrivateGame,
|
createPrivateGame,
|
||||||
getLobby,
|
getLobby,
|
||||||
updateGroup,
|
updateGroup,
|
||||||
|
getGroupByOrgCode,
|
||||||
getSubgroups,
|
getSubgroups,
|
||||||
getSubgroup,
|
getSubgroup,
|
||||||
createSubgroup,
|
createSubgroup,
|
||||||
@@ -214,6 +232,7 @@ const useGameHook = () => {
|
|||||||
createTeam,
|
createTeam,
|
||||||
updateTeam,
|
updateTeam,
|
||||||
deleteTeam,
|
deleteTeam,
|
||||||
|
disbandTeams,
|
||||||
joinTeamByCode,
|
joinTeamByCode,
|
||||||
getTeamMembers,
|
getTeamMembers,
|
||||||
leaveTeam,
|
leaveTeam,
|
||||||
@@ -223,6 +242,7 @@ const useGameHook = () => {
|
|||||||
deleteCache,
|
deleteCache,
|
||||||
resetGame,
|
resetGame,
|
||||||
resetPlayerProgress,
|
resetPlayerProgress,
|
||||||
|
resetTeamProgress,
|
||||||
getAdminWaitlist,
|
getAdminWaitlist,
|
||||||
approveAdmin,
|
approveAdmin,
|
||||||
rejectAdmin,
|
rejectAdmin,
|
||||||
|
|||||||
Reference in new issue
Block a user