Refactor UI components: update styles for consistency, changed colour scheme, dont like all the white light colours.

This commit is contained in:
bobbert committed 2026-04-09 14:04:55 +01:00
1 parent 188c581f37
commit 1465a55602
14 files changed
+556 -607

No files matched your search

+2 -2
View File
@@ -13,12 +13,12 @@ const Card = ({children, style}) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
card: { card: {
backgroundColor: '#ffffff', backgroundColor: '#313244',
borderRadius: 8, borderRadius: 8,
padding: 12, padding: 12,
marginBottom: 8, marginBottom: 8,
borderWidth: 1, borderWidth: 1,
borderColor: '#e5e7eb', borderColor: '#45475a',
}, },
}); });
+39 -45
View File
@@ -3,33 +3,27 @@ import Card from '../UI/Card';
import {Button} from '../UI/Button'; import {Button} from '../UI/Button';
const CacheCardItem = ({cache, isAdmin, isClaimed, isSelected, onEdit, onDelete, onSelect, disabled, departmentName}) => { const CacheCardItem = ({cache, isAdmin, isClaimed, isSelected, onEdit, onDelete, onSelect, disabled, departmentName}) => {
// Initialisation ------------- // Display name fallback for cache without a name
const displayName = cache.name || cache.clue || 'Unnamed Cache';
const displayName = cache.name || cache.clue || 'Unnamed Cache'; return (
<Card style={[isSelected && styles.selectedCard, isClaimed && styles.claimedCard, disabled && styles.disabledCard]}>
// State ---------------------- <View style={styles.topRow}>
// Handlers ------------------- <View style={styles.nameWrap}>
// View ----------------------- <Text style={[styles.name, isClaimed && styles.claimedText, disabled && styles.disabledText]} numberOfLines={1}>{displayName}</Text>
{isAdmin && departmentName ? (
return ( <Text style={styles.departmentText} numberOfLines={1}>Department: {departmentName}</Text>
<Card style={[isSelected && styles.selectedCard, isClaimed && styles.claimedCard, disabled && styles.disabledCard]}> ) : null}
<View style={styles.topRow}> {cache.name ? (
<View style={styles.nameWrap}> <Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text>
<Text style={[styles.name, isClaimed && styles.claimedText, disabled && styles.disabledText]} numberOfLines={1}>{displayName}</Text> ) : null}
{isAdmin && departmentName ? ( </View>
<Text style={styles.departmentText} numberOfLines={1}>Department: {departmentName}</Text> {!isAdmin && !disabled && (
) : null} <Text style={[styles.status, {color: isClaimed ? '#585b70' : '#a6e3a1'}]}>
{cache.name ? ( {isClaimed ? 'Claimed' : 'Available'}
<Text style={styles.clue} numberOfLines={1}>{cache.clue}</Text> </Text>
) : null} )}
</View> </View>
{/* Players see team-specific claim status; admins see no status */}
{!isAdmin && !disabled && (
<Text style={[styles.status, {color: isClaimed ? '#9ca3af' : '#16a34a'}]}>
{isClaimed ? 'Claimed' : 'Available'}
</Text>
)}
</View>
<View style={styles.actions}> <View style={styles.actions}>
{isAdmin ? ( {isAdmin ? (
<> <>
@@ -89,55 +83,55 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
clue: {fontSize: 13, color: '#6b7280', marginTop: 2}, clue: {fontSize: 13, color: '#6c7086', marginTop: 2},
departmentText: {fontSize: 13, color: '#2563eb', marginTop: 2, fontWeight: '600'}, departmentText: {fontSize: 13, color: '#89b4fa', marginTop: 2, fontWeight: '600'},
nameWrap: {flex: 1, marginRight: 8}, nameWrap: {flex: 1, marginRight: 8},
name: {fontSize: 15, fontWeight: '600', color: '#1f2937'}, name: {fontSize: 15, fontWeight: '600', color: '#cdd6f4'},
claimedText: {color: '#9ca3af'}, claimedText: {color: '#6c7086'},
status: {fontSize: 13, fontWeight: '600'}, status: {fontSize: 13, fontWeight: '600'},
actions: {flexDirection: 'row', gap: 8}, actions: {flexDirection: 'row', gap: 8},
editBtn: { editBtn: {
backgroundColor: '#2563eb', backgroundColor: '#89b4fa',
borderColor: '#2563eb', borderColor: '#89b4fa',
minHeight: 36, minHeight: 36,
paddingHorizontal: 14, paddingHorizontal: 14,
flex: 1, flex: 1,
}, },
deleteBtn: { deleteBtn: {
backgroundColor: '#dc2626', backgroundColor: '#f38ba8',
borderColor: '#dc2626', borderColor: '#f38ba8',
minHeight: 36, minHeight: 36,
paddingHorizontal: 14, paddingHorizontal: 14,
flex: 1, flex: 1,
}, },
selectBtn: { selectBtn: {
backgroundColor: '#16a34a', backgroundColor: '#a6e3a1',
borderColor: '#16a34a', borderColor: '#a6e3a1',
minHeight: 36, minHeight: 36,
paddingHorizontal: 14, paddingHorizontal: 14,
flex: 1, flex: 1,
}, },
selectedBtn: { selectedBtn: {
backgroundColor: '#6b7280', backgroundColor: '#6c7086',
borderColor: '#6b7280', borderColor: '#6c7086',
minHeight: 36, minHeight: 36,
paddingHorizontal: 14, paddingHorizontal: 14,
flex: 1, flex: 1,
}, },
claimedBtn: { claimedBtn: {
backgroundColor: '#d1d5db', backgroundColor: '#45475a',
borderColor: '#d1d5db', borderColor: '#45475a',
minHeight: 36, minHeight: 36,
paddingHorizontal: 14, paddingHorizontal: 14,
flex: 1, flex: 1,
}, },
claimedBtnLabel: {color: '#6b7280', fontWeight: '600', fontSize: 13}, claimedBtnLabel: {color: '#6c7086', fontWeight: '600', fontSize: 13},
selectedBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, selectedBtnLabel: {color: '#cdd6f4', fontWeight: '600', fontSize: 13},
selectedCard: {borderColor: '#2563eb', borderWidth: 2}, selectedCard: {borderColor: '#89b4fa', borderWidth: 2},
claimedCard: {opacity: 0.5}, claimedCard: {opacity: 0.5},
disabledCard: {opacity: 0.45}, disabledCard: {opacity: 0.45},
disabledText: {color: '#9ca3af'}, disabledText: {color: '#6c7086'},
btnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, btnLabel: {color: '#cdd6f4', fontWeight: '600', fontSize: 13},
}); });
export default CacheCardItem; export default CacheCardItem;
+4 -9
View File
@@ -3,15 +3,11 @@ import {StyleSheet, Text, View} from 'react-native';
import {CACHE_CLAIM_TIMER} from '../../utils/geoMath'; import {CACHE_CLAIM_TIMER} from '../../utils/geoMath';
const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming, showClaimedPopup }) => { const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming, showClaimedPopup }) => {
// Initialisation -------------
// State ----------------------
const [timeLeft, setTimeLeft] = useState(CACHE_CLAIM_TIMER); const [timeLeft, setTimeLeft] = useState(CACHE_CLAIM_TIMER);
// Use a ref for the callback so it does not appear in the dependency array // Use a ref for the callback so it does not appear in the dependency array
const onClaimRef = useRef(onClaimSuccess); const onClaimRef = useRef(onClaimSuccess);
onClaimRef.current = onClaimSuccess; onClaimRef.current = onClaimSuccess;
// Handlers -------------------
useEffect(() => { useEffect(() => {
let timer; let timer;
@@ -25,7 +21,6 @@ const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming, showClaimedPopup })
return () => clearTimeout(timer); return () => clearTimeout(timer);
}, [isClaiming, timeLeft, cache]); }, [isClaiming, timeLeft, cache]);
// View -----------------------
// Show "Cache Claimed!" popup after a successful claim // Show "Cache Claimed!" popup after a successful claim
if (showClaimedPopup) { if (showClaimedPopup) {
@@ -50,27 +45,27 @@ const ClaimTimerView = ({ cache, onClaimSuccess, isClaiming, showClaimedPopup })
const styles = StyleSheet.create({ const styles = StyleSheet.create({
overlay: { overlay: {
alignSelf: 'center', alignSelf: 'center',
backgroundColor: 'rgba(0,0,0,0.75)', backgroundColor: 'rgba(0, 0, 0, 0.75)',
paddingHorizontal: 16, paddingHorizontal: 16,
paddingVertical: 12, paddingVertical: 12,
borderRadius: 10, borderRadius: 10,
marginTop: 8, marginTop: 8,
}, },
text: { text: {
color: '#ffffff', color: '#cdd6f4',
fontSize: 16, fontSize: 16,
fontWeight: '700', fontWeight: '700',
}, },
claimedOverlay: { claimedOverlay: {
alignSelf: 'center', alignSelf: 'center',
backgroundColor: '#16a34a', backgroundColor: '#a6e3a1',
paddingHorizontal: 20, paddingHorizontal: 20,
paddingVertical: 14, paddingVertical: 14,
borderRadius: 10, borderRadius: 10,
marginTop: 8, marginTop: 8,
}, },
claimedText: { claimedText: {
color: '#ffffff', color: '#1e1e2e',
fontSize: 18, fontSize: 18,
fontWeight: '700', fontWeight: '700',
}, },
+7 -7
View File
@@ -90,8 +90,8 @@ const styles = StyleSheet.create({
container: { container: {
minHeight: 64, minHeight: 64,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: '#555555', borderTopColor: '#45475a',
backgroundColor: '#4a4a4a', backgroundColor: '#313244',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-around', justifyContent: 'space-around',
@@ -107,22 +107,22 @@ const styles = StyleSheet.create({
marginHorizontal: 4, marginHorizontal: 4,
}, },
tabActive: { tabActive: {
backgroundColor: '#5c5c5c', backgroundColor: '#45475a',
}, },
tabLeave: { tabLeave: {
backgroundColor: 'rgba(220,38,38,0.15)', backgroundColor: 'rgba(243, 139, 168, 0.15)',
}, },
tabText: { tabText: {
color: '#d1d5db', color: '#bac2de',
fontSize: 12, fontSize: 12,
fontWeight: '600', fontWeight: '600',
textAlign: 'center', textAlign: 'center',
}, },
tabTextActive: { tabTextActive: {
color: '#ffffff', color: '#cdd6f4',
}, },
tabTextLeave: { tabTextLeave: {
color: '#fca5a5', color: '#f38ba8',
}, },
}); });
+2 -2
View File
@@ -44,13 +44,13 @@ const Screen = ({ children, style, showBack = false }) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
screen: { screen: {
flex: 1, flex: 1,
backgroundColor: "#fff", backgroundColor: '#1e1e2e',
}, },
content: { content: {
flex: 1, flex: 1,
padding: 15, padding: 15,
zIndex: 1, zIndex: 1,
overflow: "hidden", overflow: 'hidden',
}, },
}); });
+2 -2
View File
@@ -47,12 +47,12 @@ const styles = StyleSheet.create({
container: { container: {
minHeight: 56, minHeight: 56,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#333333', borderBottomColor: '#45475a',
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
justifyContent: 'space-between', justifyContent: 'space-between',
paddingHorizontal: 12, paddingHorizontal: 12,
backgroundColor: '#000000', backgroundColor: '#313244',
zIndex: 10, zIndex: 10,
}, },
leftSlot: { leftSlot: {
@@ -5,39 +5,35 @@ import {Button, ButtonTray} from '../UI/Button';
import useGameHook from '../../hooks/useGameHook'; import useGameHook from '../../hooks/useGameHook';
const DepartmentSettingsScreen = ({route, navigation}) => { const DepartmentSettingsScreen = ({route, navigation}) => {
// Initialisation ------------ const {SGid, Gid} = route.params;
const {getSubgroup, updateSubgroup, getCaches, resetGame} = useGameHook();
const {SGid, Gid} = route.params; const [loading, setLoading] = useState(true);
const {getSubgroup, updateSubgroup, getCaches, resetGame} = useGameHook(); const [saving, setSaving] = useState(false);
const [deptName, setDeptName] = useState('');
// State ---------------------- const [joinCode, setJoinCode] = useState('');
const [teamsEnabled, setTeamsEnabled] = useState(false);
const [loading, setLoading] = useState(true); const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
const [saving, setSaving] = useState(false); const [cacheCount, setCacheCount] = useState(0);
const [deptName, setDeptName] = useState('');
const [joinCode, setJoinCode] = useState('');
const [teamsEnabled, setTeamsEnabled] = useState(false);
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
const [cacheCount, setCacheCount] = useState(0);
// Handlers ------------------- // Handlers -------------------
useEffect(() => { useEffect(() => {
const load = async () => { const load = async () => {
const sg = await getSubgroup(SGid); const sg = await getSubgroup(SGid);
if (sg) { if (sg) {
setDeptName(sg.SubGroupName || ''); setDeptName(sg.SubGroupName || '');
setJoinCode(sg.JoinCode || ''); setJoinCode(sg.JoinCode || '');
setTeamsEnabled(Boolean(sg.TeamsEnabled)); setTeamsEnabled(Boolean(sg.TeamsEnabled));
setCacheTriggerMeters(String(sg.CacheTriggerMeters || 20)); setCacheTriggerMeters(String(sg.CacheTriggerMeters || 20));
} }
// Load cache count for this department // Load cache count for this department
const caches = await getCaches(Gid, SGid); const caches = await getCaches(Gid, SGid);
setCacheCount((caches || []).length); setCacheCount((caches || []).length);
setLoading(false); setLoading(false);
}; };
load(); load();
}, []); }, []);
const handleSave = async () => { const handleSave = async () => {
setSaving(true); setSaving(true);
@@ -167,17 +163,17 @@ const DepartmentSettingsScreen = ({route, navigation}) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: {justifyContent: 'center', alignItems: 'center'}, center: {justifyContent: 'center', alignItems: 'center'},
formContainer: {paddingBottom: 20}, formContainer: {paddingBottom: 20},
sectionTitle: {fontSize: 20, fontWeight: '700', color: '#1f2937', marginBottom: 16}, sectionTitle: {fontSize: 20, fontWeight: '700', color: '#cdd6f4', marginBottom: 16},
label: {fontSize: 14, fontWeight: '600', color: '#374151', marginBottom: 6, marginTop: 14}, label: {fontSize: 14, fontWeight: '600', color: '#bac2de', marginBottom: 6, marginTop: 14},
input: { input: {
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
codeSection: { codeSection: {
flexDirection: 'row', flexDirection: 'row',
@@ -186,11 +182,11 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
paddingVertical: 12, paddingVertical: 12,
paddingHorizontal: 12, paddingHorizontal: 12,
backgroundColor: '#f3f4f6', backgroundColor: '#313244',
borderRadius: 8, borderRadius: 8,
}, },
codeLabel: {fontSize: 14, fontWeight: '600', color: '#374151'}, codeLabel: {fontSize: 14, fontWeight: '600', color: '#bac2de'},
codeValue: {fontSize: 16, fontWeight: '700', color: '#2563eb', letterSpacing: 2}, codeValue: {fontSize: 16, fontWeight: '700', color: '#89b4fa', letterSpacing: 2},
toggleRow: { toggleRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@@ -198,9 +194,9 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
paddingVertical: 10, paddingVertical: 10,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#e5e7eb', borderBottomColor: '#45475a',
}, },
toggleLabel: {fontSize: 14, fontWeight: '600', color: '#374151'}, toggleLabel: {fontSize: 14, fontWeight: '600', color: '#bac2de'},
infoRow: { infoRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@@ -208,20 +204,20 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
paddingVertical: 12, paddingVertical: 12,
paddingHorizontal: 12, paddingHorizontal: 12,
backgroundColor: '#f3f4f6', backgroundColor: '#313244',
borderRadius: 8, borderRadius: 8,
}, },
infoLabel: {fontSize: 14, fontWeight: '600', color: '#374151'}, infoLabel: {fontSize: 14, fontWeight: '600', color: '#bac2de'},
infoValue: {fontSize: 16, fontWeight: '700', color: '#1f2937'}, infoValue: {fontSize: 16, fontWeight: '700', color: '#cdd6f4'},
saveWrap: {marginTop: 24}, saveWrap: {marginTop: 24},
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'}, saveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
saveLabel: {color: '#ffffff', fontWeight: '600'}, saveLabel: {color: '#1e1e2e', fontWeight: '600'},
mapWrap: {marginTop: 16}, mapWrap: {marginTop: 16},
mapButton: {backgroundColor: '#2563eb', borderColor: '#2563eb'}, mapButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa'},
mapLabel: {color: '#ffffff', fontWeight: '600'}, mapLabel: {color: '#1e1e2e', fontWeight: '600'},
resetWrap: {marginTop: 30}, resetWrap: {marginTop: 30},
resetButton: {backgroundColor: '#dc2626', borderColor: '#dc2626'}, resetButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8'},
resetLabel: {color: '#ffffff', fontWeight: '600'}, resetLabel: {color: '#1e1e2e', fontWeight: '600'},
}); });
export default DepartmentSettingsScreen; export default DepartmentSettingsScreen;
+76 -84
View File
@@ -8,63 +8,58 @@ import useGameHook from '../../hooks/useGameHook';
import {getSession, setSessionTeamsEnabled} from '../../hooks/SessionStore'; import {getSession, setSessionTeamsEnabled} from '../../hooks/SessionStore';
const GameSettingsScreen = ({navigation}) => { const GameSettingsScreen = ({navigation}) => {
// Initialisation ------------ const session = getSession();
const {getLobby, updateGroup, getSubgroups, createSubgroup, deleteSubgroup, resetGame, getAdminWaitlist, approveAdmin, rejectAdmin, getUser, disbandTeams} = useGameHook();
const gameApiRef = useRef({
getLobby,
getSubgroups,
getAdminWaitlist,
getUser,
});
gameApiRef.current = {
getLobby,
getSubgroups,
getAdminWaitlist,
getUser,
};
const isMountedRef = useRef(true);
const isRefreshingRef = useRef(false);
const hasHydratedRef = useRef(false);
const session = getSession(); const [loading, setLoading] = useState(true);
const {getLobby, updateGroup, getSubgroups, createSubgroup, deleteSubgroup, resetGame, getAdminWaitlist, approveAdmin, rejectAdmin, getUser, disbandTeams} = useGameHook(); const [saving, setSaving] = useState(false);
const gameApiRef = useRef({ const [groupName, setGroupName] = useState('');
getLobby, const [businessName, setBusinessName] = useState('');
getSubgroups, const [teamsEnabled, setTeamsEnabled] = useState(session.teamsEnabled);
getAdminWaitlist, const [serverTeamsEnabled, setServerTeamsEnabled] = useState(session.teamsEnabled);
getUser, const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
}); const [adminJoinCode, setAdminJoinCode] = useState('');
gameApiRef.current = { const [orgJoinCode, setOrgJoinCode] = useState('');
getLobby, const [memberJoinCode, setMemberJoinCode] = useState('');
getSubgroups, const [waitlist, setWaitlist] = useState([]);
getAdminWaitlist, const [waitlistNames, setWaitlistNames] = useState({});
getUser, const [isBusinessGroup, setIsBusinessGroup] = useState(Boolean(session.isBusiness));
}; const [subgroupList, setSubgroupList] = useState([]);
const isMountedRef = useRef(true); const [newDeptName, setNewDeptName] = useState('');
const isRefreshingRef = useRef(false);
const hasHydratedRef = useRef(false);
// State ----------------------
const [loading, setLoading] = useState(true);
const [saving, setSaving] = useState(false);
const [groupName, setGroupName] = useState('');
const [businessName, setBusinessName] = useState('');
const [teamsEnabled, setTeamsEnabled] = useState(session.teamsEnabled);
const [serverTeamsEnabled, setServerTeamsEnabled] = useState(session.teamsEnabled);
const [cacheTriggerMeters, setCacheTriggerMeters] = useState('20');
const [adminJoinCode, setAdminJoinCode] = useState('');
const [orgJoinCode, setOrgJoinCode] = useState('');
const [memberJoinCode, setMemberJoinCode] = useState('');
const [waitlist, setWaitlist] = useState([]);
const [waitlistNames, setWaitlistNames] = useState({});
const [isBusinessGroup, setIsBusinessGroup] = useState(Boolean(session.isBusiness));
// Business subgroup management
const [subgroupList, setSubgroupList] = useState([]);
const [newDeptName, setNewDeptName] = useState('');
// Handlers ------------------- // Handlers -------------------
useEffect(() => () => { useEffect(() => () => {
isMountedRef.current = false; isMountedRef.current = false;
}, []); }, []);
const refreshSettings = useCallback(async ({showLoader = false} = {}) => { const refreshSettings = useCallback(async ({showLoader = false} = {}) => {
const activeSession = getSession(); const activeSession = getSession();
if (!activeSession.currentGid) { if (!activeSession.currentGid) {
if (isMountedRef.current) setLoading(false); if (isMountedRef.current) setLoading(false);
return; return;
} }
// Guard against re-entrant focus refresh calls. // Guard against re-entrant focus refresh calls.
if (isRefreshingRef.current) return; if (isRefreshingRef.current) return;
isRefreshingRef.current = true; isRefreshingRef.current = true;
if (showLoader && isMountedRef.current) setLoading(true); if (showLoader && isMountedRef.current) setLoading(true);
try { try {
const [group, sgs, waitlistRows] = await Promise.all([ const [group, sgs, waitlistRows] = await Promise.all([
@@ -78,7 +73,7 @@ const GameSettingsScreen = ({navigation}) => {
if (group) { if (group) {
setGroupName(group.GroupName || ''); setGroupName(group.GroupName || '');
setBusinessName(group.BusinessOrSchoolName || ''); setBusinessName(group.BusinessOrSchoolName || '');
// Organisation mode is based on organisation profile data, not generic org join code presence. // Organisation mode
setIsBusinessGroup(Boolean(group.BusinessOrSchoolName)); setIsBusinessGroup(Boolean(group.BusinessOrSchoolName));
const serverTeams = Boolean(group.TeamsEnabled); const serverTeams = Boolean(group.TeamsEnabled);
@@ -487,18 +482,18 @@ const GameSettingsScreen = ({navigation}) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: {justifyContent: 'center', alignItems: 'center'}, center: {justifyContent: 'center', alignItems: 'center'},
formContainer: {paddingBottom: 20}, formContainer: {paddingBottom: 20},
sectionTitle: {fontSize: 20, fontWeight: '700', color: '#1f2937', marginBottom: 16}, sectionTitle: {fontSize: 20, fontWeight: '700', color: '#cdd6f4', marginBottom: 16},
label: {fontSize: 14, fontWeight: '600', color: '#374151', marginBottom: 6, marginTop: 14}, label: {fontSize: 14, fontWeight: '600', color: '#bac2de', marginBottom: 6, marginTop: 14},
body: {color: '#4b5563', fontSize: 15}, body: {color: '#bac2de', fontSize: 15},
input: { input: {
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
toggleRow: { toggleRow: {
flexDirection: 'row', flexDirection: 'row',
@@ -507,9 +502,9 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
paddingVertical: 10, paddingVertical: 10,
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#e5e7eb', borderBottomColor: '#45475a',
}, },
toggleLabel: {fontSize: 14, fontWeight: '600', color: '#374151'}, toggleLabel: {fontSize: 14, fontWeight: '600', color: '#bac2de'},
codeSection: { codeSection: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
@@ -517,47 +512,44 @@ const styles = StyleSheet.create({
marginTop: 16, marginTop: 16,
paddingVertical: 12, paddingVertical: 12,
paddingHorizontal: 12, paddingHorizontal: 12,
backgroundColor: '#f3f4f6', backgroundColor: '#313244',
borderRadius: 8, borderRadius: 8,
}, },
codeLabel: {fontSize: 14, fontWeight: '600', color: '#374151'}, codeLabel: {fontSize: 14, fontWeight: '600', color: '#bac2de'},
codeValue: {fontSize: 16, fontWeight: '700', color: '#2563eb', letterSpacing: 2}, codeValue: {fontSize: 16, fontWeight: '700', color: '#89b4fa', letterSpacing: 2},
saveWrap: {marginTop: 24}, saveWrap: {marginTop: 24},
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'}, saveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
saveLabel: {color: '#ffffff', fontWeight: '600'}, saveLabel: {color: '#1e1e2e', fontWeight: '600'},
// Department / subgroup styles
deptSection: {marginTop: 30}, deptSection: {marginTop: 30},
deptIndexLabel: {fontSize: 14, fontWeight: '700', color: '#374151', marginBottom: 8}, deptIndexLabel: {fontSize: 14, fontWeight: '700', color: '#bac2de', marginBottom: 8},
deptCardRow: {flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8}, deptCardRow: {flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8},
deptCardInfo: {flex: 1, paddingRight: 10}, deptCardInfo: {flex: 1, paddingRight: 10},
deptCardActions: {flexDirection: 'row', gap: 6, flex: 0}, deptCardActions: {flexDirection: 'row', gap: 6, flex: 0},
deptInfoLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 2}, deptInfoLabel: {fontSize: 12, fontWeight: '600', color: '#6c7086', marginBottom: 2},
deptName: {fontSize: 15, fontWeight: '700', color: '#1f2937'}, deptName: {fontSize: 15, fontWeight: '700', color: '#cdd6f4'},
deptCode: {fontSize: 13, fontWeight: '600', color: '#2563eb', marginTop: 2, letterSpacing: 1}, deptCode: {fontSize: 13, fontWeight: '600', color: '#89b4fa', marginTop: 2, letterSpacing: 1},
deptDeleteButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 10}, deptDeleteButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 0, paddingHorizontal: 10},
deptOpenButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', minHeight: 36, flex: 0, paddingHorizontal: 10}, deptOpenButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa', minHeight: 36, flex: 0, paddingHorizontal: 10},
deptBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, deptBtnLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
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: '#a6e3a1', borderColor: '#a6e3a1', flex: 0, paddingHorizontal: 16},
createDeptLabel: {color: '#ffffff', fontWeight: '600'}, createDeptLabel: {color: '#1e1e2e', fontWeight: '600'},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 10, fontSize: 14}, emptyText: {color: '#6c7086', textAlign: 'center', marginTop: 10, fontSize: 14},
// Admin waitlist styles
waitlistSection: {marginTop: 30}, waitlistSection: {marginTop: 30},
waitlistRow: { waitlistRow: {
flexDirection: 'row', flexDirection: 'row',
justifyContent: 'space-between', justifyContent: 'space-between',
alignItems: 'center', alignItems: 'center',
}, },
waitlistName: {fontSize: 15, fontWeight: '600', color: '#1f2937', flex: 1}, waitlistName: {fontSize: 15, fontWeight: '600', color: '#cdd6f4', flex: 1},
waitlistActions: {flexDirection: 'row', gap: 6}, waitlistActions: {flexDirection: 'row', gap: 6},
approveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a', minHeight: 36, flex: 0, paddingHorizontal: 10}, approveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1', minHeight: 36, flex: 0, paddingHorizontal: 10},
approveBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, approveBtnLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
rejectButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 10}, rejectButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 0, paddingHorizontal: 10},
rejectBtnLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, rejectBtnLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
// Reset game styles
resetSection: {marginTop: 30}, resetSection: {marginTop: 30},
resetButton: {backgroundColor: '#dc2626', borderColor: '#dc2626'}, resetButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8'},
resetLabel: {color: '#ffffff', fontWeight: '600'}, resetLabel: {color: '#1e1e2e', fontWeight: '600'},
}); });
export default GameSettingsScreen; export default GameSettingsScreen;
+21 -24
View File
@@ -411,13 +411,13 @@ const LeaderboardScreen = ({navigation, route}) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: {justifyContent: 'center', alignItems: 'center'}, center: {justifyContent: 'center', alignItems: 'center'},
container: {padding: 0}, container: {padding: 0},
body: {color: '#4b5563', fontSize: 15}, body: {color: '#bac2de', fontSize: 15},
departmentTitle: {fontSize: 20, fontWeight: '700', color: '#1f2937', paddingHorizontal: 15, paddingTop: 15, marginBottom: 6}, departmentTitle: {fontSize: 20, fontWeight: '700', color: '#cdd6f4', paddingHorizontal: 15, paddingTop: 15, marginBottom: 6},
openLabel: {fontSize: 13, fontWeight: '700', color: '#2563eb'}, openLabel: {fontSize: 13, fontWeight: '700', color: '#89b4fa'},
tabRow: { tabRow: {
flexDirection: 'row', flexDirection: 'row',
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#e5e7eb', borderBottomColor: '#45475a',
}, },
tabButton: { tabButton: {
flex: 1, flex: 1,
@@ -426,47 +426,44 @@ const styles = StyleSheet.create({
}, },
tabButtonActive: { tabButtonActive: {
borderBottomWidth: 2, borderBottomWidth: 2,
borderBottomColor: '#2563eb', borderBottomColor: '#89b4fa',
}, },
tabLabel: {fontSize: 15, fontWeight: '600', color: '#6b7280'}, tabLabel: {fontSize: 15, fontWeight: '600', color: '#6c7086'},
tabLabelActive: {color: '#2563eb'}, tabLabelActive: {color: '#89b4fa'},
listSection: {flex: 1, paddingHorizontal: 15, paddingTop: 10}, listSection: {flex: 1, paddingHorizontal: 15, paddingTop: 10},
rankRow: { rankRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 10, gap: 10,
}, },
rank: {fontSize: 16, fontWeight: '700', color: '#374151', width: 32}, rank: {fontSize: 16, fontWeight: '700', color: '#bac2de', width: 32},
rankName: {flex: 1, fontSize: 15, fontWeight: '600', color: '#1f2937'}, rankName: {flex: 1, fontSize: 15, fontWeight: '600', color: '#cdd6f4'},
score: {fontSize: 14, fontWeight: '600', color: '#16a34a'}, score: {fontSize: 14, fontWeight: '600', color: '#a6e3a1'},
subRow: { subRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
paddingLeft: 42, paddingLeft: 42,
paddingVertical: 6, paddingVertical: 6,
borderTopWidth: 1, borderTopWidth: 1,
borderTopColor: '#f3f4f6', borderTopColor: '#45475a',
}, },
subName: {fontSize: 13, color: '#4b5563'}, subName: {fontSize: 13, color: '#bac2de'},
subScore: {fontSize: 13, fontWeight: '600', color: '#16a34a', marginRight: 8}, subScore: {fontSize: 13, fontWeight: '600', color: '#a6e3a1', marginRight: 8},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14}, emptyText: {color: '#6c7086', textAlign: 'center', marginTop: 30, fontSize: 14},
// Admin delete team button
deleteTeamWrap: {marginTop: 8, paddingLeft: 42, flexDirection: 'row', gap: 8}, deleteTeamWrap: {marginTop: 8, paddingLeft: 42, flexDirection: 'row', gap: 8},
resetTeamButton: {backgroundColor: '#f59e0b', borderColor: '#f59e0b', minHeight: 36, flex: 1}, resetTeamButton: {backgroundColor: '#f4a460', borderColor: '#f4a460', minHeight: 36, flex: 1},
deleteTeamButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 1}, deleteTeamButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 1},
deleteTeamLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, deleteTeamLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
// Admin reset mini button (inline)
resetMiniButton: { resetMiniButton: {
backgroundColor: '#f59e0b', backgroundColor: '#f4a460',
borderRadius: 6, borderRadius: 6,
paddingHorizontal: 10, paddingHorizontal: 10,
paddingVertical: 4, paddingVertical: 4,
marginLeft: 6, marginLeft: 6,
}, },
resetMiniLabel: {color: '#ffffff', fontWeight: '600', fontSize: 12}, resetMiniLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 12},
// Current user self-highlight (light green card / sub-row) selfCard: {backgroundColor: 'rgba(166, 227, 161, 0.15)', borderColor: '#a6e3a1'},
selfCard: {backgroundColor: '#dcfce7', borderColor: '#86efac'}, selfSubRow: {backgroundColor: 'rgba(166, 227, 161, 0.15)'},
selfSubRow: {backgroundColor: '#dcfce7'},
}); });
export default LeaderboardScreen; export default LeaderboardScreen;
+11 -19
View File
@@ -5,16 +5,9 @@ import Screen from '../layout/Screen';
import {Button, ButtonTray} from '../UI/Button'; import {Button, ButtonTray} from '../UI/Button';
const LoginScreen = ({ navigation }) => { const LoginScreen = ({ navigation }) => {
// Initialisation -------------
const { email, password, error, isLoading, setEmail, setPassword, login } = useAuthHook(); const { email, password, error, isLoading, setEmail, setPassword, login } = useAuthHook();
// State ----------------------
const [submitting, setSubmitting] = useState(false); const [submitting, setSubmitting] = useState(false);
// Handlers -------------------
const handleLogin = async () => { const handleLogin = async () => {
setSubmitting(true); setSubmitting(true);
const user = await login(); const user = await login();
@@ -29,8 +22,6 @@ const LoginScreen = ({ navigation }) => {
navigation.reset({ index: 0, routes: [{ name: 'Game' }] }); navigation.reset({ index: 0, routes: [{ name: 'Game' }] });
}; };
// View -----------------------
return ( return (
<Screen style={styles.container}> <Screen style={styles.container}>
<View style={styles.content}> <View style={styles.content}>
@@ -39,7 +30,7 @@ const LoginScreen = ({ navigation }) => {
<TextInput <TextInput
style={styles.input} style={styles.input}
placeholder="Email" placeholder="Email"
placeholderTextColor="#9ca3af" placeholderTextColor="#6c7086"
autoCapitalize="none" autoCapitalize="none"
keyboardType="email-address" keyboardType="email-address"
value={email} value={email}
@@ -49,7 +40,7 @@ const LoginScreen = ({ navigation }) => {
<TextInput <TextInput
style={styles.input} style={styles.input}
placeholder="Password" placeholder="Password"
placeholderTextColor="#9ca3af" placeholderTextColor="#6c7086"
secureTextEntry secureTextEntry
value={password} value={password}
onChangeText={setPassword} onChangeText={setPassword}
@@ -87,33 +78,34 @@ const styles = StyleSheet.create({
fontWeight: 'bold', fontWeight: 'bold',
textAlign: 'center', textAlign: 'center',
marginBottom: 10, marginBottom: 10,
color: '#cdd6f4',
}, },
input: { input: {
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
errorText: { errorText: {
color: '#dc2626', color: '#f38ba8',
textAlign: 'center', textAlign: 'center',
marginTop: 5, marginTop: 5,
}, },
submitButton: { submitButton: {
backgroundColor: '#2563eb', backgroundColor: '#89b4fa',
borderColor: '#2563eb', borderColor: '#89b4fa',
}, },
submitText: { submitText: {
color: '#ffffff', color: '#1e1e2e',
fontWeight: '600', fontWeight: '600',
}, },
linkText: { linkText: {
textAlign: 'center', textAlign: 'center',
color: '#2563eb', color: '#89b4fa',
fontWeight: '600', fontWeight: '600',
paddingVertical: 10, paddingVertical: 10,
}, },
+112 -120
View File
@@ -17,63 +17,59 @@ const DEFAULT_REGION = {latitude: 51.5074, longitude: -0.1278, latitudeDelta: 0.
const MapScreen = ({navigation, route}) => { const MapScreen = ({navigation, route}) => {
// Initialisation ------------ const session = getSession();
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, createPrivateGame, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook();
const session = getSession(); // Stable ref to prevent infinite re-render loops
const {getCaches, claimCache, upsertCache, deleteCache, joinPrivateGame, createPrivateGame, getLobby, getUser, getSubgroups, getGroupByOrgCode} = useGameHook(); const getCachesRef = useRef(getCaches);
getCachesRef.current = getCaches;
const lastAppliedRouteDepartmentRef = useRef(null);
// Stable ref to prevent infinite re-render loops const [inGame, setInGame] = useState(Boolean(session.currentGid));
const getCachesRef = useRef(getCaches); const [isAdmin, setIsAdmin] = useState(session.isAcceptedAdmin);
getCachesRef.current = getCaches; const [userLocation, setUserLocation] = useState(null);
const lastAppliedRouteDepartmentRef = useRef(null); const [heading, setHeading] = useState(null);
const [playerLoading, setPlayerLoading] = useState(true);
const [cacheRecords, setCacheRecords] = useState([]);
const [error, setError] = useState('');
const [gameCode, setGameCode] = useState('');
const [groupInfo, setGroupInfo] = useState(null);
const [selectedCacheId, setSelectedCacheIdState] = useState(session.selectedCacheId);
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
const [subgroups, setSubgroups] = useState([]);
// State ---------------------- // Business join flow state
const [joinStep, setJoinStep] = useState(null);
const [orgCode, setOrgCode] = useState('');
const [deptCode, setDeptCode] = useState('');
const [matchedGroup, setMatchedGroup] = useState(null);
const [joinError, setJoinError] = useState('');
const [inGame, setInGame] = useState(Boolean(session.currentGid)); // Admin cache creation / editing state
const [isAdmin, setIsAdmin] = useState(session.isAcceptedAdmin); const [isCreating, setIsCreating] = useState(false);
const [userLocation, setUserLocation] = useState(null); const [editingCacheId, setEditingCacheId] = useState(null);
const [heading, setHeading] = useState(null); const [newCoord, setNewCoord] = useState(null);
const [playerLoading, setPlayerLoading] = useState(true); const [newName, setNewName] = useState('');
const [cacheRecords, setCacheRecords] = useState([]); const [newClue, setNewClue] = useState('');
const [error, setError] = useState(''); const [selectedCacheSubgroupId, setSelectedCacheSubgroupId] = useState(null);
const [gameCode, setGameCode] = useState(''); const [departmentDropdownOpen, setDepartmentDropdownOpen] = useState(false);
const [groupInfo, setGroupInfo] = useState(null);
const [selectedCacheId, setSelectedCacheIdState] = useState(session.selectedCacheId);
const [claimedPopupVisible, setClaimedPopupVisible] = useState(false);
const [subgroups, setSubgroups] = useState([]);
// Business join flow state const claimDistance = groupInfo?.CacheTriggerMeters || 20;
const [joinStep, setJoinStep] = useState(null); // null | 'deptCode' const isPlayer = inGame && !isAdmin;
const [orgCode, setOrgCode] = useState(''); const requiresTeam = Boolean(isPlayer && groupInfo?.TeamsEnabled && !session.currentTid);
const [deptCode, setDeptCode] = useState(''); const EMPTY_CACHES = useMemo(() => [], []);
const [matchedGroup, setMatchedGroup] = useState(null);
const [joinError, setJoinError] = useState('');
// Admin cache creation / editing state // Caches not yet claimed by the current user's team (or user if no team)
const [isCreating, setIsCreating] = useState(false); const activeCachesForPlayer = useMemo(() => {
const [editingCacheId, setEditingCacheId] = useState(null); if (!isPlayer) return EMPTY_CACHES;
const [newCoord, setNewCoord] = useState(null); return cacheRecords.filter((cache) => {
const [newName, setNewName] = useState(''); const claims = cache.Claims || [];
const [newClue, setNewClue] = useState(''); if (session.currentTid) {
const [selectedCacheSubgroupId, setSelectedCacheSubgroupId] = useState(null); return !claims.some((c) => c.Tid === session.currentTid);
const [departmentDropdownOpen, setDepartmentDropdownOpen] = useState(false); }
return !claims.some((c) => c.Uid === session.currentUid);
const claimDistance = groupInfo?.CacheTriggerMeters || 20; });
const isPlayer = inGame && !isAdmin; }, [cacheRecords, isPlayer, session.currentTid, session.currentUid, EMPTY_CACHES]);
const requiresTeam = Boolean(isPlayer && groupInfo?.TeamsEnabled && !session.currentTid);
const EMPTY_CACHES = useMemo(() => [], []);
// Caches not yet claimed by the current user's team (or user if no team)
const activeCachesForPlayer = useMemo(() => {
if (!isPlayer) return EMPTY_CACHES;
return cacheRecords.filter((cache) => {
const claims = cache.Claims || [];
if (session.currentTid) {
return !claims.some((c) => c.Tid === session.currentTid);
}
return !claims.some((c) => c.Uid === session.currentUid);
});
}, [cacheRecords, isPlayer, session.currentTid, session.currentUid, EMPTY_CACHES]);
const {visibleCaches, isClaiming, setIsClaiming} = usePlayerGame( const {visibleCaches, isClaiming, setIsClaiming} = usePlayerGame(
(isPlayer && !requiresTeam) ? userLocation : null, (isPlayer && !requiresTeam) ? userLocation : null,
@@ -83,31 +79,29 @@ const MapScreen = ({navigation, route}) => {
selectedCacheId, selectedCacheId,
); );
const mapRegion = userLocation const mapRegion = userLocation
? {...userLocation, latitudeDelta: 0.01, longitudeDelta: 0.01} ? {...userLocation, latitudeDelta: 0.01, longitudeDelta: 0.01}
: DEFAULT_REGION; : DEFAULT_REGION;
// Handlers ------------------- useEffect(() => {
if (!session.currentGid) return;
getLobby(session.currentGid).then(setGroupInfo);
getSubgroups(session.currentGid).then(setSubgroups);
}, [inGame]);
useEffect(() => { // Default member subgroup SGid for cache creation (first non-admin subgroup)
if (!session.currentGid) return; const departments = useMemo(
getLobby(session.currentGid).then(setGroupInfo); () => subgroups.filter((sg) => !sg.IsAdminGroup),
getSubgroups(session.currentGid).then(setSubgroups); [subgroups],
}, [inGame]); );
const defaultMemberSGid = departments[0]?.SGid || null;
// Default member subgroup SGid for cache creation (first non-admin subgroup) const departmentNamesById = useMemo(() => {
const departments = useMemo( const map = {};
() => subgroups.filter((sg) => !sg.IsAdminGroup), for (const subgroup of departments) {
[subgroups], map[String(subgroup.SGid)] = subgroup.SubGroupName || `Department ${subgroup.SGid}`;
); }
const defaultMemberSGid = departments[0]?.SGid || null; return map;
const departmentNamesById = useMemo(() => { }, [departments]);
const map = {};
for (const subgroup of departments) {
map[String(subgroup.SGid)] = subgroup.SubGroupName || `Department ${subgroup.SGid}`;
}
return map;
}, [departments]);
useEffect(() => { useEffect(() => {
if (!isAdmin || departments.length === 0) return; if (!isAdmin || departments.length === 0) return;
@@ -378,6 +372,8 @@ const MapScreen = ({navigation, route}) => {
return claims.some((c) => c.Uid === session.currentUid); return claims.some((c) => c.Uid === session.currentUid);
}; };
const showAdminDepartmentLabel = Boolean(isAdmin && groupInfo?.BusinessOrSchoolName);
// View ----------------------- // View -----------------------
// Not in a game — Business flow // Not in a game — Business flow
@@ -668,7 +664,7 @@ const MapScreen = ({navigation, route}) => {
key={cache.id} key={cache.id}
cache={cache} cache={cache}
isAdmin={true} isAdmin={true}
departmentName={departmentNamesById[String(cache.subgroupId)] || 'Unassigned'} departmentName={showAdminDepartmentLabel ? (departmentNamesById[String(cache.subgroupId)] || 'Unassigned') : null}
onEdit={handleEditCache} onEdit={handleEditCache}
onDelete={handleDeleteCache} onDelete={handleDeleteCache}
/> />
@@ -755,33 +751,32 @@ const styles = StyleSheet.create({
center: {justifyContent: 'center', alignItems: 'center'}, center: {justifyContent: 'center', alignItems: 'center'},
containerMap: {padding: 0}, containerMap: {padding: 0},
mapContainer: {height: 200}, mapContainer: {height: 200},
error: {color: '#dc2626', fontSize: 15}, error: {color: '#f38ba8', fontSize: 15},
loadingText: {color: '#6b7280', fontSize: 14, marginTop: 10}, loadingText: {color: '#6c7086', fontSize: 14, marginTop: 10},
inputRow: {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}, fullRow: {width: '100%', paddingHorizontal: 20},
codeInput: { codeInput: {
flex: 1, flex: 1,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
joinButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', flex: 0, paddingHorizontal: 20}, joinButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa', flex: 0, paddingHorizontal: 20},
joinLabel: {color: '#ffffff', fontWeight: '600'}, joinLabel: {color: '#1e1e2e', fontWeight: '600'},
createButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'}, createButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
createLabel: {color: '#ffffff', fontWeight: '600'}, createLabel: {color: '#1e1e2e', fontWeight: '600'},
// Business join flow styles bizTitle: {fontSize: 22, fontWeight: '700', color: '#cdd6f4', marginBottom: 6, textAlign: 'center'},
bizTitle: {fontSize: 22, fontWeight: '700', color: '#1f2937', marginBottom: 6, textAlign: 'center'}, bizSubtitle: {fontSize: 14, color: '#bac2de', textAlign: 'center', marginBottom: 20, paddingHorizontal: 20},
bizSubtitle: {fontSize: 14, color: '#6b7280', textAlign: 'center', marginBottom: 20, paddingHorizontal: 20}, joinError: {color: '#f38ba8', textAlign: 'center', marginTop: 8, marginBottom: 4, fontSize: 14, paddingHorizontal: 20},
joinError: {color: '#dc2626', textAlign: 'center', marginTop: 8, marginBottom: 4, fontSize: 14, paddingHorizontal: 20}, backButton: {backgroundColor: '#6c7086', borderColor: '#6c7086'},
backButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'}, backLabel: {color: '#cdd6f4', fontWeight: '600'},
backLabel: {color: '#ffffff', fontWeight: '600'},
warning: { warning: {
color: '#dc2626', color: '#f38ba8',
fontWeight: 'bold', fontWeight: 'bold',
textAlign: 'center', textAlign: 'center',
paddingVertical: 8, paddingVertical: 8,
@@ -791,7 +786,7 @@ const styles = StyleSheet.create({
position: 'absolute', position: 'absolute',
top: 10, top: 10,
left: 10, left: 10,
backgroundColor: 'rgba(0,0,0,0.6)', backgroundColor: 'rgba(0, 0, 0, 0.6)',
borderRadius: 6, borderRadius: 6,
width: 36, width: 36,
height: 36, height: 36,
@@ -799,38 +794,36 @@ const styles = StyleSheet.create({
justifyContent: 'center', justifyContent: 'center',
zIndex: 10, zIndex: 10,
}, },
expandIcon: {color: '#ffffff', fontSize: 18, fontWeight: '700'}, expandIcon: {color: '#cdd6f4', fontSize: 18, fontWeight: '700'},
// Admin create button + scrollable cache list
createCacheWrap: { createCacheWrap: {
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
backgroundColor: '#ffffff', backgroundColor: '#313244',
borderBottomWidth: 1, borderBottomWidth: 1,
borderBottomColor: '#e5e7eb', borderBottomColor: '#45475a',
}, },
createCacheButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', flex: 0}, createCacheButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa', flex: 0},
createCacheLabel: {color: '#ffffff', fontWeight: '600'}, createCacheLabel: {color: '#1e1e2e', fontWeight: '600'},
cacheListWrap: {flex: 1, backgroundColor: '#f3f4f6'}, cacheListWrap: {flex: 1, backgroundColor: '#1e1e2e'},
cacheListContent: {paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12}, cacheListContent: {paddingHorizontal: 12, paddingTop: 10, paddingBottom: 12},
// Cache editor form
formSection: {flex: 1, paddingHorizontal: 12, paddingVertical: 10}, formSection: {flex: 1, paddingHorizontal: 12, paddingVertical: 10},
formInput: { formInput: {
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
marginBottom: 10, marginBottom: 10,
}, },
departmentLabel: {fontSize: 13, fontWeight: '600', color: '#374151', marginBottom: 6}, departmentLabel: {fontSize: 13, fontWeight: '600', color: '#bac2de', marginBottom: 6},
departmentSelector: { departmentSelector: {
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
backgroundColor: '#ffffff', backgroundColor: '#313244',
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
flexDirection: 'row', flexDirection: 'row',
@@ -838,28 +831,27 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
marginBottom: 8, marginBottom: 8,
}, },
departmentSelectorText: {color: '#1f2937', fontSize: 15, fontWeight: '500'}, departmentSelectorText: {color: '#cdd6f4', fontSize: 15, fontWeight: '500'},
departmentSelectorChevron: {color: '#6b7280', fontSize: 12, fontWeight: '700'}, departmentSelectorChevron: {color: '#6c7086', fontSize: 12, fontWeight: '700'},
departmentMenu: { departmentMenu: {
borderWidth: 1, borderWidth: 1,
borderColor: '#e5e7eb', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
backgroundColor: '#ffffff', backgroundColor: '#313244',
marginBottom: 10, marginBottom: 10,
overflow: 'hidden', overflow: 'hidden',
}, },
departmentOption: {paddingHorizontal: 12, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#f3f4f6'}, departmentOption: {paddingHorizontal: 12, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#45475a'},
departmentOptionText: {color: '#374151', fontSize: 14}, departmentOptionText: {color: '#cdd6f4', fontSize: 14},
coordRow: {flexDirection: 'row', gap: 10}, coordRow: {flexDirection: 'row', gap: 10},
coordField: {flex: 1}, coordField: {flex: 1},
coordLabel: {fontSize: 12, fontWeight: '600', color: '#6b7280', marginBottom: 4}, coordLabel: {fontSize: 12, fontWeight: '600', color: '#6c7086', marginBottom: 4},
saveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'}, saveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
saveLabel: {color: '#ffffff', fontWeight: '600'}, saveLabel: {color: '#1e1e2e', fontWeight: '600'},
cancelButton: {backgroundColor: '#6b7280', borderColor: '#6b7280'}, cancelButton: {backgroundColor: '#6c7086', borderColor: '#6c7086'},
cancelLabel: {color: '#ffffff', fontWeight: '600'}, cancelLabel: {color: '#cdd6f4', fontWeight: '600'},
// Player cache section cacheSection: {flex: 1, backgroundColor: '#1e1e2e', paddingHorizontal: 12, paddingTop: 10},
cacheSection: {flex: 1, backgroundColor: '#f3f4f6', paddingHorizontal: 12, paddingTop: 10}, emptyText: {color: '#6c7086', textAlign: 'center', marginTop: 20, fontSize: 14},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 20, fontSize: 14},
mapWrap: {flex: 1}, mapWrap: {flex: 1},
}); });
+16 -18
View File
@@ -287,40 +287,38 @@ const PlayersScreen = ({navigation, route}) => {
const styles = StyleSheet.create({ const styles = StyleSheet.create({
center: {justifyContent: 'center', alignItems: 'center', flex: 1}, center: {justifyContent: 'center', alignItems: 'center', flex: 1},
container: {padding: 0}, container: {padding: 0},
body: {color: '#4b5563', fontSize: 15}, body: {color: '#bac2de', fontSize: 15},
sectionTitle: { sectionTitle: {
fontSize: 20, fontSize: 20,
fontWeight: '700', fontWeight: '700',
color: '#1f2937', color: '#cdd6f4',
marginBottom: 12, marginBottom: 12,
paddingHorizontal: 15, paddingHorizontal: 15,
paddingTop: 15, paddingTop: 15,
}, },
openLabel: {fontSize: 13, fontWeight: '700', color: '#2563eb'}, openLabel: {fontSize: 13, fontWeight: '700', color: '#89b4fa'},
listSection: {flex: 1, paddingHorizontal: 15}, listSection: {flex: 1, paddingHorizontal: 15},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14}, emptyText: {color: '#6c7086', textAlign: 'center', marginTop: 30, fontSize: 14},
playerRow: { playerRow: {
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
marginBottom: 8, marginBottom: 8,
}, },
playerName: {fontSize: 15, fontWeight: '600', color: '#1f2937', flex: 1}, playerName: {fontSize: 15, fontWeight: '600', color: '#cdd6f4', flex: 1},
playerMeta: {flexDirection: 'row', alignItems: 'center', gap: 6}, playerMeta: {flexDirection: 'row', alignItems: 'center', gap: 6},
leaderBadge: {fontSize: 11, color: '#2563eb', fontWeight: '700'}, leaderBadge: {fontSize: 11, color: '#89b4fa', fontWeight: '700'},
teamCodeBadge: {fontSize: 12, fontWeight: '700', color: '#16a34a', letterSpacing: 1}, teamCodeBadge: {fontSize: 12, fontWeight: '700', color: '#a6e3a1', letterSpacing: 1},
adminBadge: {fontSize: 12, fontWeight: '700', color: '#dc2626'}, adminBadge: {fontSize: 12, fontWeight: '700', color: '#f38ba8'},
waitlistBadge: {fontSize: 11, fontWeight: '600', color: '#f59e0b'}, waitlistBadge: {fontSize: 11, fontWeight: '600', color: '#f4a460'},
actionRow: {flexDirection: 'row', gap: 8}, actionRow: {flexDirection: 'row', gap: 8},
resetButton: {backgroundColor: '#f59e0b', borderColor: '#f59e0b', minHeight: 36, flex: 1, paddingHorizontal: 10}, resetButton: {backgroundColor: '#f4a460', borderColor: '#f4a460', minHeight: 36, flex: 1, paddingHorizontal: 10},
removeButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 1, paddingHorizontal: 10}, removeButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 1, paddingHorizontal: 10},
actionLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, actionLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
departmentName: {fontSize: 14, color: '#6b7280', fontStyle: 'italic'}, departmentName: {fontSize: 14, color: '#6c7086', fontStyle: 'italic'},
// Waitlisted admin card tint — light red so they read as pending, not regular players waitlistCard: {backgroundColor: 'rgba(243, 139, 168, 0.15)', borderColor: '#f38ba8'},
waitlistCard: {backgroundColor: '#FFD1DC', borderColor: '#fca5a5'}, approveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1', minHeight: 36, flex: 1, paddingHorizontal: 10},
// Approve / deny buttons for waitlisted admins denyButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 1, paddingHorizontal: 10},
approveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a', minHeight: 36, flex: 1, paddingHorizontal: 10},
denyButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 1, paddingHorizontal: 10},
}); });
export default PlayersScreen; export default PlayersScreen;
+158 -165
View File
@@ -5,182 +5,175 @@ import Screen from '../layout/Screen';
import {Button, ButtonTray} from '../UI/Button'; import {Button, ButtonTray} from '../UI/Button';
const RegisterScreen = ({navigation}) => { const RegisterScreen = ({navigation}) => {
// Initialisation ------------ const {
username, email, password, confirmPassword, accountType,
error, isLoading,
setUsername, setEmail, setPassword, setConfirmPassword, setAccountType,
register,
} = useAuthHook();
const { const [submitting, setSubmitting] = useState(false);
username, email, password, confirmPassword, accountType,
error, isLoading,
setUsername, setEmail, setPassword, setConfirmPassword, setAccountType,
register,
} = useAuthHook();
// State ---------------------- const handleRegister = async () => {
setSubmitting(true);
const user = await register();
setSubmitting(false);
if (!user) return;
if (user.IsAcceptedAdmin) {
navigation.reset({index: 0, routes: [{name: 'Admin'}]});
return;
}
navigation.reset({index: 0, routes: [{name: 'Game'}]});
};
const [submitting, setSubmitting] = useState(false); return (
<Screen style={styles.container}>
<View style={styles.content}>
<Text style={styles.title}>Create Account</Text>
// Handlers ------------------- <TextInput
style={styles.input}
placeholder="Username"
placeholderTextColor="#6c7086"
value={username}
onChangeText={setUsername}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Email"
placeholderTextColor="#6c7086"
autoCapitalize="none"
keyboardType="email-address"
value={email}
onChangeText={setEmail}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Password"
placeholderTextColor="#6c7086"
secureTextEntry
value={password}
onChangeText={setPassword}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Confirm Password"
placeholderTextColor="#6c7086"
secureTextEntry
value={confirmPassword}
onChangeText={setConfirmPassword}
editable={!isLoading}
/>
const handleRegister = async () => { <View style={styles.typeRow}>
setSubmitting(true); <Pressable
const user = await register(); style={[styles.typeButton, accountType === 'Individual' && styles.typeButtonActive]}
setSubmitting(false); onPress={() => setAccountType('Individual')}
if (!user) return; disabled={isLoading}
if (user.IsAcceptedAdmin) { >
navigation.reset({index: 0, routes: [{name: 'Admin'}]}); <Text style={[styles.typeText, accountType === 'Individual' && styles.typeTextActive]}>
return; Individual
} </Text>
navigation.reset({index: 0, routes: [{name: 'Game'}]}); </Pressable>
}; <Pressable
style={[styles.typeButton, accountType === 'Business/School' && styles.typeButtonActive]}
onPress={() => setAccountType('Business/School')}
disabled={isLoading}
>
<Text style={[styles.typeText, accountType === 'Business/School' && styles.typeTextActive]}>
Business
</Text>
</Pressable>
</View>
// View ----------------------- {error ? <Text style={styles.errorText}>{error}</Text> : null}
return ( <ButtonTray>
<Screen style={styles.container}> <Button
<View style={styles.content}> label={submitting ? 'Creating...' : 'Create Account'}
<Text style={styles.title}>Create Account</Text> onClick={handleRegister}
styleButton={styles.submitButton}
styleLabel={styles.submitText}
/>
</ButtonTray>
<TextInput <Pressable onPress={() => navigation.navigate('Auth', {screen: 'LoginScreen'})}>
style={styles.input} <Text style={styles.linkText}>Back to Login</Text>
placeholder="Username" </Pressable>
placeholderTextColor="#9ca3af" </View>
value={username} </Screen>
onChangeText={setUsername} );
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Email"
placeholderTextColor="#9ca3af"
autoCapitalize="none"
keyboardType="email-address"
value={email}
onChangeText={setEmail}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Password"
placeholderTextColor="#9ca3af"
secureTextEntry
value={password}
onChangeText={setPassword}
editable={!isLoading}
/>
<TextInput
style={styles.input}
placeholder="Confirm Password"
placeholderTextColor="#9ca3af"
secureTextEntry
value={confirmPassword}
onChangeText={setConfirmPassword}
editable={!isLoading}
/>
<View style={styles.typeRow}>
<Pressable
style={[styles.typeButton, accountType === 'Individual' && styles.typeButtonActive]}
onPress={() => setAccountType('Individual')}
disabled={isLoading}
>
<Text style={[styles.typeText, accountType === 'Individual' && styles.typeTextActive]}>
Individual
</Text>
</Pressable>
<Pressable
style={[styles.typeButton, accountType === 'Business/School' && styles.typeButtonActive]}
onPress={() => setAccountType('Business/School')}
disabled={isLoading}
>
<Text style={[styles.typeText, accountType === 'Business/School' && styles.typeTextActive]}>
Business
</Text>
</Pressable>
</View>
{error ? <Text style={styles.errorText}>{error}</Text> : null}
<ButtonTray>
<Button
label={submitting ? 'Creating...' : 'Create Account'}
onClick={handleRegister}
styleButton={styles.submitButton}
styleLabel={styles.submitText}
/>
</ButtonTray>
<Pressable onPress={() => navigation.navigate('Auth', {screen: 'LoginScreen'})}>
<Text style={styles.linkText}>Back to Login</Text>
</Pressable>
</View>
</Screen>
);
}; };
const styles = StyleSheet.create({ const styles = StyleSheet.create({
container: { container: {
justifyContent: 'center', justifyContent: 'center',
}, },
content: { content: {
gap: 12, gap: 12,
}, },
title: { title: {
fontSize: 24, fontSize: 24,
fontWeight: 'bold', fontWeight: 'bold',
textAlign: 'center', textAlign: 'center',
marginBottom: 10, marginBottom: 10,
}, color: '#cdd6f4',
input: { },
borderWidth: 1, input: {
borderColor: '#d1d5db', borderWidth: 1,
borderRadius: 8, borderColor: '#45475a',
paddingHorizontal: 12, borderRadius: 8,
paddingVertical: 10, paddingHorizontal: 12,
fontSize: 16, paddingVertical: 10,
color: '#1f2937', fontSize: 16,
backgroundColor: '#ffffff', color: '#cdd6f4',
}, backgroundColor: '#313244',
typeRow: { },
flexDirection: 'row', typeRow: {
gap: 10, flexDirection: 'row',
}, gap: 10,
typeButton: { },
flex: 1, typeButton: {
borderWidth: 1, flex: 1,
borderColor: '#cbd5e1', borderWidth: 1,
borderRadius: 8, borderColor: '#45475a',
alignItems: 'center', borderRadius: 8,
paddingVertical: 10, alignItems: 'center',
}, paddingVertical: 10,
typeButtonActive: { },
backgroundColor: '#2563eb', typeButtonActive: {
borderColor: '#2563eb', backgroundColor: '#89b4fa',
}, borderColor: '#89b4fa',
typeText: { },
color: '#1f2937', typeText: {
fontWeight: '600', color: '#bac2de',
}, fontWeight: '600',
typeTextActive: { },
color: '#ffffff', typeTextActive: {
}, color: '#1e1e2e',
errorText: { },
color: '#dc2626', errorText: {
textAlign: 'center', color: '#f38ba8',
marginTop: 5, textAlign: 'center',
}, marginTop: 5,
submitButton: { },
backgroundColor: '#2563eb', submitButton: {
borderColor: '#2563eb', backgroundColor: '#89b4fa',
}, borderColor: '#89b4fa',
submitText: { },
color: '#ffffff', submitText: {
fontWeight: '600', color: '#1e1e2e',
}, fontWeight: '600',
linkText: { },
textAlign: 'center', linkText: {
color: '#2563eb', textAlign: 'center',
fontWeight: '600', color: '#89b4fa',
paddingVertical: 10, fontWeight: '600',
}, paddingVertical: 10,
},
}); });
export default RegisterScreen; export default RegisterScreen;
+62 -62
View File
@@ -7,40 +7,34 @@ import useGameHook from '../../hooks/useGameHook';
import {getSession, setSessionTeam} from '../../hooks/SessionStore'; import {getSession, setSessionTeam} from '../../hooks/SessionStore';
const TeamScreen = () => { const TeamScreen = () => {
// Initialisation ------------ const session = getSession();
const isAdmin = session.isAcceptedAdmin;
const isBusiness = Boolean(session.isBusiness);
const {getLobby, getSubgroup, getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook();
const session = getSession(); const [activeTid, setActiveTid] = useState(session.currentTid);
const isAdmin = session.isAcceptedAdmin; const [teamCode, setTeamCode] = useState('');
const isBusiness = Boolean(session.isBusiness); const [team, setTeam] = useState(null);
const {getLobby, getSubgroup, getTeam, createTeam, joinTeamByCode, getTeamMembers, leaveTeam, getUser, updateUser, updateTeam, getAdminWaitlist} = useGameHook(); const [members, setMembers] = useState([]);
const [memberNames, setMemberNames] = useState({});
const [loading, setLoading] = useState(true);
const [teamsDisabled, setTeamsDisabled] = useState(false);
const [onAdminWaitlist, setOnAdminWaitlist] = useState(false);
const [editingName, setEditingName] = useState(false);
const [teamNameDraft, setTeamNameDraft] = useState('');
// State ---------------------- const loadTeamData = async (tid) => {
const t = await getTeam(tid);
const [activeTid, setActiveTid] = useState(session.currentTid); setTeam(t);
const [teamCode, setTeamCode] = useState(''); const ms = await getTeamMembers(tid);
const [team, setTeam] = useState(null); setMembers(ms || []);
const [members, setMembers] = useState([]); const names = {};
const [memberNames, setMemberNames] = useState({}); for (const m of (ms || [])) {
const [loading, setLoading] = useState(true); const u = await getUser(m.Uid);
const [teamsDisabled, setTeamsDisabled] = useState(false); if (u) names[m.Uid] = u.username;
const [onAdminWaitlist, setOnAdminWaitlist] = useState(false); }
const [editingName, setEditingName] = useState(false); setMemberNames(names);
const [teamNameDraft, setTeamNameDraft] = useState(''); };
// Handlers -------------------
const loadTeamData = async (tid) => {
const t = await getTeam(tid);
setTeam(t);
const ms = await getTeamMembers(tid);
setMembers(ms || []);
const names = {};
for (const m of (ms || [])) {
const u = await getUser(m.Uid);
if (u) names[m.Uid] = u.username;
}
setMemberNames(names);
};
useEffect(() => { useEffect(() => {
const load = async () => { const load = async () => {
@@ -304,66 +298,64 @@ 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}, disabledWrap: {paddingHorizontal: 30, alignItems: 'center', opacity: 0.45},
disabledTitle: {fontSize: 20, fontWeight: '700', color: '#6b7280', marginBottom: 10, textAlign: 'center'}, disabledTitle: {fontSize: 20, fontWeight: '700', color: '#6c7086', marginBottom: 10, textAlign: 'center'},
disabledBody: {fontSize: 15, color: '#9ca3af', textAlign: 'center'}, disabledBody: {fontSize: 15, color: '#6c7086', textAlign: 'center'},
// Shown when the user has not yet joined a game notInGameText: {color: '#6c7086', 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: '#313244', opacity: 0.5},
inputDisabled: {backgroundColor: '#f3f4f6', opacity: 0.5},
inputRow: {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}, fullRow: {width: '100%', paddingHorizontal: 20},
codeInput: { codeInput: {
flex: 1, flex: 1,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 10, paddingVertical: 10,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
joinButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', flex: 0, paddingHorizontal: 20}, joinButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa', flex: 0, paddingHorizontal: 20},
joinLabel: {color: '#ffffff', fontWeight: '600'}, joinLabel: {color: '#1e1e2e', fontWeight: '600'},
createButton: {backgroundColor: '#16a34a', borderColor: '#16a34a'}, createButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1'},
createLabel: {color: '#ffffff', fontWeight: '600'}, createLabel: {color: '#1e1e2e', fontWeight: '600'},
waitlistText: { waitlistText: {
fontSize: 18, fontSize: 18,
fontWeight: '700', fontWeight: '700',
color: '#374151', color: '#cdd6f4',
textAlign: 'center', textAlign: 'center',
paddingHorizontal: 30, paddingHorizontal: 30,
}, },
teamHeader: { teamHeader: {
backgroundColor: '#374151', backgroundColor: '#45475a',
paddingVertical: 14, paddingVertical: 14,
paddingHorizontal: 16, paddingHorizontal: 16,
flexDirection: 'row', flexDirection: 'row',
alignItems: 'center', alignItems: 'center',
gap: 8, gap: 8,
}, },
teamCodeLabel: {color: '#d1d5db', fontSize: 15, fontWeight: '600'}, teamCodeLabel: {color: '#bac2de', fontSize: 15, fontWeight: '600'},
teamCodeValue: {color: '#ffffff', fontSize: 17, fontWeight: '700', letterSpacing: 2}, teamCodeValue: {color: '#cdd6f4', fontSize: 17, fontWeight: '700', letterSpacing: 2},
// Team name / rename section teamNameSection: {paddingHorizontal: 15, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#45475a'},
teamNameSection: {paddingHorizontal: 15, paddingVertical: 10, borderBottomWidth: 1, borderBottomColor: '#e5e7eb'}, teamNameText: {fontSize: 18, fontWeight: '700', color: '#cdd6f4', flex: 1},
teamNameText: {fontSize: 18, fontWeight: '700', color: '#1f2937', flex: 1},
renameRow: {flexDirection: 'row', alignItems: 'center', gap: 8}, renameRow: {flexDirection: 'row', alignItems: 'center', gap: 8},
renameInput: { renameInput: {
flex: 1, flex: 1,
borderWidth: 1, borderWidth: 1,
borderColor: '#d1d5db', borderColor: '#45475a',
borderRadius: 8, borderRadius: 8,
paddingHorizontal: 12, paddingHorizontal: 12,
paddingVertical: 8, paddingVertical: 8,
fontSize: 16, fontSize: 16,
color: '#1f2937', color: '#cdd6f4',
backgroundColor: '#ffffff', backgroundColor: '#313244',
}, },
renameButton: {backgroundColor: '#2563eb', borderColor: '#2563eb', flex: 0, minHeight: 36, paddingHorizontal: 14}, renameButton: {backgroundColor: '#89b4fa', borderColor: '#89b4fa', flex: 0, minHeight: 36, paddingHorizontal: 14},
renameLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, renameLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
renameSaveButton: {backgroundColor: '#16a34a', borderColor: '#16a34a', flex: 0, minHeight: 36, paddingHorizontal: 12}, renameSaveButton: {backgroundColor: '#a6e3a1', borderColor: '#a6e3a1', flex: 0, minHeight: 36, paddingHorizontal: 12},
renameSaveLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, renameSaveLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
renameCancelButton: {backgroundColor: '#6b7280', borderColor: '#6b7280', flex: 0, minHeight: 36, paddingHorizontal: 12}, renameCancelButton: {backgroundColor: '#6c7086', borderColor: '#6c7086', flex: 0, minHeight: 36, paddingHorizontal: 12},
renameCancelLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, renameCancelLabel: {color: '#cdd6f4', fontWeight: '600', fontSize: 13},
memberList: {flex: 1, paddingHorizontal: 15, paddingTop: 10}, memberList: {flex: 1, paddingHorizontal: 15, paddingTop: 10},
memberRow: { memberRow: {
flexDirection: 'row', flexDirection: 'row',
@@ -371,8 +363,16 @@ const styles = StyleSheet.create({
alignItems: 'center', alignItems: 'center',
}, },
memberInfo: {flex: 1, flexDirection: 'row', alignItems: 'center', gap: 6}, memberInfo: {flex: 1, flexDirection: 'row', alignItems: 'center', gap: 6},
memberName: {fontSize: 16, color: '#1f2937', fontWeight: '600'}, memberName: {fontSize: 16, color: '#cdd6f4', fontWeight: '600'},
leaderBadge: {fontSize: 12, color: '#2563eb', fontWeight: '700'}, leaderBadge: {fontSize: 12, color: '#89b4fa', fontWeight: '700'},
kickButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8', minHeight: 36, flex: 0, paddingHorizontal: 14},
kickLabel: {color: '#1e1e2e', fontWeight: '600', fontSize: 13},
emptyText: {color: '#6c7086', textAlign: 'center', marginTop: 30, fontSize: 14},
leaveWrap: {paddingHorizontal: 0, paddingTop: 10, paddingBottom: 16},
leaveButton: {backgroundColor: '#f38ba8', borderColor: '#f38ba8'},
leaveLabel: {color: '#1e1e2e', fontWeight: '600'},
selfCard: {backgroundColor: 'rgba(166, 227, 161, 0.15)', borderColor: '#a6e3a1'},
});
kickButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 14}, kickButton: {backgroundColor: '#dc2626', borderColor: '#dc2626', minHeight: 36, flex: 0, paddingHorizontal: 14},
kickLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13}, kickLabel: {color: '#ffffff', fontWeight: '600', fontSize: 13},
emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14}, emptyText: {color: '#9ca3af', textAlign: 'center', marginTop: 30, fontSize: 14},