refactor username

Changed username to be the first name "_" user id
This commit is contained in:
suddenlyoranges committed 2026-04-09 21:33:57 +01:00
1 parent 53fddc8220
commit 07b12cdd0f
3 files changed
+337 -243

No files matched your search

-3
View File
@@ -1,8 +1,5 @@
# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
# Environment variables (contains secrets)
.env
.env.local
# dependencies # dependencies
node_modules/ node_modules/
@@ -1,10 +1,18 @@
import {useCallback, useEffect, useRef, useState} from "react"; import { useCallback, useEffect, useRef, useState } from "react";
import {ActivityIndicator, Image, ScrollView, StyleSheet, Text, TextInput, View} from "react-native"; import {
ActivityIndicator,
Image,
ScrollView,
StyleSheet,
Text,
TextInput,
View,
} from "react-native";
import Screen from "../../layout/Screen"; import Screen from "../../layout/Screen";
import {Button, ButtonTray} from "../../UI/Button"; import { Button, ButtonTray } from "../../UI/Button";
import useGlobalHook from "../../../hooks/useGlobalHook"; import useGlobalHook from "../../../hooks/useGlobalHook";
import {getSession} from "../../../hooks/SessionStore"; import { getSession } from "../../../hooks/SessionStore";
import {GAME_MODE} from "../../../utils/gameConstants"; import { GAME_MODE } from "../../../utils/gameConstants";
const EMPTY_FORM = { const EMPTY_FORM = {
UserFirstname: "", UserFirstname: "",
@@ -14,7 +22,7 @@ const EMPTY_FORM = {
UserImageURL: "", UserImageURL: "",
}; };
const GlobalProfileScreen = ({navigation}) => { const GlobalProfileScreen = ({ navigation }) => {
// Initialisations --------------------- // Initialisations ---------------------
const session = getSession(); const session = getSession();
@@ -44,45 +52,52 @@ const GlobalProfileScreen = ({navigation}) => {
// Handlers ---------------------------- // Handlers ----------------------------
const hydrateProfile = useCallback(async (options = {}) => { const hydrateProfile = useCallback(
setIsLoading(true); async (options = {}) => {
setError(""); setIsLoading(true);
setError("");
if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) { if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) {
navigation.replace("MapScreen"); navigation.replace("MapScreen");
return; return;
} }
if (!globalApiRef.current.isGlobalApiReady()) { if (!globalApiRef.current.isGlobalApiReady()) {
setError("Global API URL is not configured yet. Add it in src/components/API/api.json."); setError(
"Global API URL is not configured yet. Add it in src/components/API/api.json.",
);
setIsLoading(false);
return;
}
const globalUserId = session.currentGlobalUserId ?? session.currentUid;
const latest = await globalApiRef.current.getUser(globalUserId, options);
if (!latest) {
setError(
"Global profile not found. Tap Join Global Game first to initialise it.",
);
setIsLoading(false);
return;
}
setProfile({
UserFirstname: latest.UserFirstname || "",
UserLastname: latest.UserLastname || "",
UserPhone: latest.UserPhone || "00000",
UserUsername: latest.UserUsername || "",
UserImageURL: latest.UserImageURL || defaultGlobalProfileImageUrl,
});
setIsLoading(false); setIsLoading(false);
return; },
} [
defaultGlobalProfileImageUrl,
const globalUserId = session.currentGlobalUserId ?? session.currentUid; navigation,
const latest = await globalApiRef.current.getUser(globalUserId, options); session.currentGameMode,
if (!latest) { session.currentGlobalUserId,
setError("Global profile not found. Tap Join Global Game first to initialise it."); session.currentUid,
setIsLoading(false); session.isBusiness,
return; ],
} );
setProfile({
UserFirstname: latest.UserFirstname || "",
UserLastname: latest.UserLastname || "",
UserPhone: latest.UserPhone || "00000",
UserUsername: latest.UserUsername || "",
UserImageURL: latest.UserImageURL || defaultGlobalProfileImageUrl,
});
setIsLoading(false);
}, [
defaultGlobalProfileImageUrl,
navigation,
session.currentGameMode,
session.currentGlobalUserId,
session.currentUid,
session.isBusiness,
]);
useEffect(() => { useEffect(() => {
hydrateProfile(); hydrateProfile();
@@ -98,19 +113,24 @@ const GlobalProfileScreen = ({navigation}) => {
UserFirstname: profile.UserFirstname.trim() || "Global", UserFirstname: profile.UserFirstname.trim() || "Global",
UserLastname: profile.UserLastname.trim() || "User", UserLastname: profile.UserLastname.trim() || "User",
UserPhone: profile.UserPhone.trim() || "00000", UserPhone: profile.UserPhone.trim() || "00000",
UserUsername: profile.UserUsername.trim() || `global_${globalUserId}`, UserUsername: `${profile.UserFirstname.trim() || "Global"}_${globalUserId}`,
UserImageURL: profile.UserImageURL.trim() || defaultGlobalProfileImageUrl, UserImageURL: profile.UserImageURL.trim() || defaultGlobalProfileImageUrl,
UserTimestamp: Date.now(), UserTimestamp: Date.now(),
}; };
const updated = await globalApiRef.current.updateUser(globalUserId, payload); const updated = await globalApiRef.current.updateUser(
globalUserId,
payload,
);
if (!updated) { if (!updated) {
setError("Failed to save profile. Please try again."); setError("Failed to save profile. Please try again.");
setIsSaving(false); setIsSaving(false);
return; return;
} }
const latest = await globalApiRef.current.getUser(globalUserId, {forceRefresh: true}); const latest = await globalApiRef.current.getUser(globalUserId, {
forceRefresh: true,
});
setProfile({ setProfile({
UserFirstname: latest?.UserFirstname || payload.UserFirstname, UserFirstname: latest?.UserFirstname || payload.UserFirstname,
UserLastname: latest?.UserLastname || payload.UserLastname, UserLastname: latest?.UserLastname || payload.UserLastname,
@@ -140,7 +160,9 @@ const GlobalProfileScreen = ({navigation}) => {
<View style={styles.avatarWrap}> <View style={styles.avatarWrap}>
<Image <Image
source={{uri: profile.UserImageURL || defaultGlobalProfileImageUrl}} source={{
uri: profile.UserImageURL || defaultGlobalProfileImageUrl,
}}
style={styles.avatar} style={styles.avatar}
/> />
</View> </View>
@@ -152,7 +174,7 @@ const GlobalProfileScreen = ({navigation}) => {
placeholderTextColor="#9ca3af" placeholderTextColor="#9ca3af"
value={profile.UserFirstname} value={profile.UserFirstname}
onChangeText={(value) => onChangeText={(value) =>
setProfile((prev) => ({...prev, UserFirstname: value})) setProfile((prev) => ({ ...prev, UserFirstname: value }))
} }
/> />
@@ -163,7 +185,7 @@ const GlobalProfileScreen = ({navigation}) => {
placeholderTextColor="#9ca3af" placeholderTextColor="#9ca3af"
value={profile.UserLastname} value={profile.UserLastname}
onChangeText={(value) => onChangeText={(value) =>
setProfile((prev) => ({...prev, UserLastname: value})) setProfile((prev) => ({ ...prev, UserLastname: value }))
} }
/> />
@@ -174,7 +196,7 @@ const GlobalProfileScreen = ({navigation}) => {
placeholderTextColor="#9ca3af" placeholderTextColor="#9ca3af"
value={profile.UserPhone} value={profile.UserPhone}
onChangeText={(value) => onChangeText={(value) =>
setProfile((prev) => ({...prev, UserPhone: value})) setProfile((prev) => ({ ...prev, UserPhone: value }))
} }
/> />
@@ -185,7 +207,7 @@ const GlobalProfileScreen = ({navigation}) => {
placeholderTextColor="#9ca3af" placeholderTextColor="#9ca3af"
value={profile.UserImageURL} value={profile.UserImageURL}
onChangeText={(value) => onChangeText={(value) =>
setProfile((prev) => ({...prev, UserImageURL: value})) setProfile((prev) => ({ ...prev, UserImageURL: value }))
} }
autoCapitalize="none" autoCapitalize="none"
/> />
@@ -254,4 +276,3 @@ const styles = StyleSheet.create({
}); });
export default GlobalProfileScreen; export default GlobalProfileScreen;
+265 -189
View File
@@ -1,4 +1,4 @@
import {useCallback, useMemo} from "react"; import { useCallback, useMemo } from "react";
import API from "../components/API/API"; import API from "../components/API/API";
import globalApiConfig from "../components/API/api.json"; import globalApiConfig from "../components/API/api.json";
import { import {
@@ -9,10 +9,14 @@ import {
} from "../utils/globalDataCache"; } from "../utils/globalDataCache";
const GLOBAL_BASE = String( const GLOBAL_BASE = String(
process.env.EXPO_PUBLIC_GLOBAL_API_BASE || globalApiConfig.GLOBAL_API_BASE_URL || "", process.env.EXPO_PUBLIC_GLOBAL_API_BASE ||
globalApiConfig.GLOBAL_API_BASE_URL ||
"",
).trim(); ).trim();
const GLOBAL_KEY = String( const GLOBAL_KEY = String(
process.env.EXPO_PUBLIC_GLOBAL_API_KEY || globalApiConfig.GLOBAL_API_KEY || "", process.env.EXPO_PUBLIC_GLOBAL_API_KEY ||
globalApiConfig.GLOBAL_API_KEY ||
"",
).trim(); ).trim();
const DEFAULT_GLOBAL_PROFILE_IMAGE_URL = const DEFAULT_GLOBAL_PROFILE_IMAGE_URL =
String(globalApiConfig.DEFAULT_GLOBAL_PROFILE_IMAGE_URL || "").trim() || String(globalApiConfig.DEFAULT_GLOBAL_PROFILE_IMAGE_URL || "").trim() ||
@@ -90,10 +94,10 @@ const withMinLength = (value, min, fallback) => {
const splitUsername = (username) => { const splitUsername = (username) => {
const cleaned = String(username || "").trim(); const cleaned = String(username || "").trim();
if (!cleaned) return {first: "Global", last: "User"}; if (!cleaned) return { first: "Global", last: "User" };
const parts = cleaned.split(/[\s._-]+/).filter(Boolean); const parts = cleaned.split(/[\s._-]+/).filter(Boolean);
if (parts.length === 1) { if (parts.length === 1) {
return {first: parts[0], last: "User"}; return { first: parts[0], last: "User" };
} }
return { return {
first: parts[0], first: parts[0],
@@ -104,11 +108,16 @@ const splitUsername = (username) => {
const buildGlobalUserPayload = (userId, privateUser = null) => { const buildGlobalUserPayload = (userId, privateUser = null) => {
const privateUsername = firstNonEmpty(privateUser?.username); const privateUsername = firstNonEmpty(privateUser?.username);
const nameParts = splitUsername(privateUsername); const nameParts = splitUsername(privateUsername);
const syncedUsername = `mad_uid_${userId}`; const firstName = firstNonEmpty(
privateUser?.firstName,
nameParts.first,
"Global",
);
const syncedUsername = `${firstName}_${userId}`;
return { return {
UserID: userId, UserID: userId,
UserFirstname: firstNonEmpty(privateUser?.firstName, nameParts.first, "Global"), UserFirstname: firstName,
UserLastname: firstNonEmpty(privateUser?.lastName, nameParts.last, "User"), UserLastname: firstNonEmpty(privateUser?.lastName, nameParts.last, "User"),
// The live global API validates phone length >= 12. // The live global API validates phone length >= 12.
UserPhone: withMinLength(privateUser?.phone, 12, "000000000000"), UserPhone: withMinLength(privateUser?.phone, 12, "000000000000"),
@@ -146,215 +155,282 @@ const useGlobalHook = () => {
// Handlers ---------------------------- // Handlers ----------------------------
// Events // Events
const getPublicEvents = useCallback(async (options = {}) => { const getPublicEvents = useCallback(
if (!isGlobalApiReady()) return []; async (options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.publicEvents, return getOrFetchGlobalData(
async () => { CACHE_KEY.publicEvents,
const response = await API.get(withKey(endpoints.events)); async () => {
if (!response.isSuccess) return []; const response = await API.get(withKey(endpoints.events));
const rows = unwrapList(response.result); if (!response.isSuccess) return [];
return rows.filter((e) => e.EventIspublic); const rows = unwrapList(response.result);
}, return rows.filter((e) => e.EventIspublic);
{ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh}, },
); { ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh },
}, [endpoints.events]); );
},
[endpoints.events],
);
const getEvent = useCallback(async (eventId, options = {}) => { const getEvent = useCallback(
if (!isGlobalApiReady()) return null; async (eventId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return null;
CACHE_KEY.event(eventId), return getOrFetchGlobalData(
async () => { CACHE_KEY.event(eventId),
const response = await API.get(withKey(`${endpoints.events}/${eventId}`)); async () => {
if (!response.isSuccess) return null; const response = await API.get(
return unwrapSingle(response.result); withKey(`${endpoints.events}/${eventId}`),
}, );
{ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return null;
); return unwrapSingle(response.result);
}, [endpoints.events]); },
{ ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh },
);
},
[endpoints.events],
);
// Players // Players
const getPlayersByEvent = useCallback(async (eventId, options = {}) => { const getPlayersByEvent = useCallback(
if (!isGlobalApiReady()) return []; async (eventId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.playersByEvent(eventId), return getOrFetchGlobalData(
async () => { CACHE_KEY.playersByEvent(eventId),
const response = await API.get(withKey(`${endpoints.players}/events/${eventId}`)); async () => {
if (!response.isSuccess) return []; const response = await API.get(
return unwrapList(response.result); withKey(`${endpoints.players}/events/${eventId}`),
}, );
{ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return [];
); return unwrapList(response.result);
}, [endpoints.players]); },
{ ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh },
);
},
[endpoints.players],
);
const joinEvent = useCallback(async (data) => { const joinEvent = useCallback(
if (!isGlobalApiReady()) return null; async (data) => {
const response = await API.post(withKey(endpoints.players), data); if (!isGlobalApiReady()) return null;
if (!response.isSuccess) return null; const response = await API.post(withKey(endpoints.players), data);
const joined = unwrapSingle(response.result); if (!response.isSuccess) return null;
const eventId = data?.PlayerEventID ?? joined?.PlayerEventID; const joined = unwrapSingle(response.result);
if (eventId !== undefined && eventId !== null) { const eventId = data?.PlayerEventID ?? joined?.PlayerEventID;
clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId)); if (eventId !== undefined && eventId !== null) {
} clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId));
return joined; }
}, [endpoints.players]); return joined;
},
[endpoints.players],
);
// Caches // Caches
const getCachesByEvent = useCallback(async (eventId, options = {}) => { const getCachesByEvent = useCallback(
if (!isGlobalApiReady()) return []; async (eventId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.cachesByEvent(eventId), return getOrFetchGlobalData(
async () => { CACHE_KEY.cachesByEvent(eventId),
const response = await API.get(withKey(`${endpoints.caches}/events/${eventId}`)); async () => {
if (!response.isSuccess) return []; const response = await API.get(
return unwrapList(response.result); withKey(`${endpoints.caches}/events/${eventId}`),
}, );
{ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return [];
); return unwrapList(response.result);
}, [endpoints.caches]); },
{ ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh },
);
},
[endpoints.caches],
);
const getCache = useCallback(async (cacheId, options = {}) => { const getCache = useCallback(
if (!isGlobalApiReady()) return null; async (cacheId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return null;
CACHE_KEY.cacheById(cacheId), return getOrFetchGlobalData(
async () => { CACHE_KEY.cacheById(cacheId),
const response = await API.get(withKey(`${endpoints.caches}/${cacheId}`)); async () => {
if (!response.isSuccess) return null; const response = await API.get(
return unwrapSingle(response.result); withKey(`${endpoints.caches}/${cacheId}`),
}, );
{ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return null;
); return unwrapSingle(response.result);
}, [endpoints.caches]); },
{ ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh },
);
},
[endpoints.caches],
);
// Finds // Finds
const getFindsByEvent = useCallback(async (eventId, options = {}) => { const getFindsByEvent = useCallback(
if (!isGlobalApiReady()) return []; async (eventId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.findsByEvent(eventId), return getOrFetchGlobalData(
async () => { CACHE_KEY.findsByEvent(eventId),
const response = await API.get(withKey(`${endpoints.finds}/events/${eventId}`)); async () => {
if (!response.isSuccess) return []; const response = await API.get(
return unwrapList(response.result); withKey(`${endpoints.finds}/events/${eventId}`),
}, );
{ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return [];
); return unwrapList(response.result);
}, [endpoints.finds]); },
{ ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh },
);
},
[endpoints.finds],
);
const getFindsByPlayer = useCallback(async (playerId, options = {}) => { const getFindsByPlayer = useCallback(
if (!isGlobalApiReady()) return []; async (playerId, options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.findsByPlayer(playerId), return getOrFetchGlobalData(
async () => { CACHE_KEY.findsByPlayer(playerId),
const response = await API.get(withKey(`${endpoints.finds}/players/${playerId}`)); async () => {
if (!response.isSuccess) return []; const response = await API.get(
return unwrapList(response.result); withKey(`${endpoints.finds}/players/${playerId}`),
}, );
{ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh}, if (!response.isSuccess) return [];
); return unwrapList(response.result);
}, [endpoints.finds]); },
{ ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh },
);
},
[endpoints.finds],
);
const logFind = useCallback(async (data) => { const logFind = useCallback(
if (!isGlobalApiReady()) return null; async (data) => {
const response = await API.post(withKey(endpoints.finds), data); if (!isGlobalApiReady()) return null;
if (!response.isSuccess) return null; const response = await API.post(withKey(endpoints.finds), data);
const find = unwrapSingle(response.result); if (!response.isSuccess) return null;
if (data?.FindPlayerID !== undefined && data?.FindPlayerID !== null) { const find = unwrapSingle(response.result);
clearGlobalDataCache(CACHE_KEY.findsByPlayer(data.FindPlayerID)); if (data?.FindPlayerID !== undefined && data?.FindPlayerID !== null) {
} clearGlobalDataCache(CACHE_KEY.findsByPlayer(data.FindPlayerID));
// Event ID is not always present in payload, so clear all event-level find caches. }
clearGlobalDataCacheByPrefix("global:finds:event:"); // Event ID is not always present in payload, so clear all event-level find caches.
return find; clearGlobalDataCacheByPrefix("global:finds:event:");
}, [endpoints.finds]); return find;
},
[endpoints.finds],
);
// Users // Users
const getUsers = useCallback(async (options = {}) => { const getUsers = useCallback(
if (!isGlobalApiReady()) return []; async (options = {}) => {
return getOrFetchGlobalData( if (!isGlobalApiReady()) return [];
CACHE_KEY.users, return getOrFetchGlobalData(
async () => { CACHE_KEY.users,
const response = await API.get(withKey(endpoints.users)); async () => {
if (!response.isSuccess) return []; const response = await API.get(withKey(endpoints.users));
return unwrapList(response.result); if (!response.isSuccess) return [];
}, return unwrapList(response.result);
{ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh}, },
); { ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh },
}, [endpoints.users]); );
},
[endpoints.users],
);
const getUser = useCallback(async (userId, options = {}) => { const getUser = useCallback(
if (!isGlobalApiReady()) return null; async (userId, options = {}) => {
if (!isGlobalApiReady()) return null;
const key = CACHE_KEY.userById(userId); const key = CACHE_KEY.userById(userId);
return getOrFetchGlobalData( return getOrFetchGlobalData(
key, key,
async () => { async () => {
// Some global API deployments expose list-only user routes. // Some global API deployments expose list-only user routes.
const byIdResponse = await API.get(withKey(`${endpoints.users}/${userId}`)); const byIdResponse = await API.get(
if (byIdResponse.isSuccess) { withKey(`${endpoints.users}/${userId}`),
const single = unwrapSingle(byIdResponse.result); );
if (single) return single; if (byIdResponse.isSuccess) {
} const single = unwrapSingle(byIdResponse.result);
if (single) return single;
}
const users = await getUsers({forceRefresh: options.forceRefresh}); const users = await getUsers({ forceRefresh: options.forceRefresh });
return users.find((user) => String(user.UserID) === String(userId)) || null; return (
}, users.find((user) => String(user.UserID) === String(userId)) || null
{ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh}, );
); },
}, [endpoints.users, getUsers]); { ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh },
);
},
[endpoints.users, getUsers],
);
const createUser = useCallback(async (payload) => { const createUser = useCallback(
if (!isGlobalApiReady()) return null; async (payload) => {
const response = await API.post(withKey(endpoints.users), payload); if (!isGlobalApiReady()) return null;
if (!response.isSuccess) return null; const response = await API.post(withKey(endpoints.users), payload);
const created = unwrapSingle(response.result); if (!response.isSuccess) return null;
const createdUserId = created?.UserID ?? payload?.UserID; const created = unwrapSingle(response.result);
if (createdUserId !== undefined && createdUserId !== null) { const createdUserId = created?.UserID ?? payload?.UserID;
if (createdUserId !== undefined && createdUserId !== null) {
writeGlobalDataCache(
CACHE_KEY.userById(createdUserId),
created || payload,
CACHE_TTL.users,
);
}
clearGlobalDataCache(CACHE_KEY.users);
return created;
},
[endpoints.users],
);
const updateUser = useCallback(
async (userId, payload) => {
if (!isGlobalApiReady()) return null;
const response = await API.put(
withKey(`${endpoints.users}/${userId}`),
payload,
);
if (!response.isSuccess) return null;
const updated = unwrapSingle(response.result) || payload;
writeGlobalDataCache( writeGlobalDataCache(
CACHE_KEY.userById(createdUserId), CACHE_KEY.userById(userId),
created || payload, updated,
CACHE_TTL.users, CACHE_TTL.users,
); );
} clearGlobalDataCache(CACHE_KEY.users);
clearGlobalDataCache(CACHE_KEY.users); return updated;
return created; },
}, [endpoints.users]); [endpoints.users],
);
const updateUser = useCallback(async (userId, payload) => {
if (!isGlobalApiReady()) return null;
const response = await API.put(withKey(`${endpoints.users}/${userId}`), payload);
if (!response.isSuccess) return null;
const updated = unwrapSingle(response.result) || payload;
writeGlobalDataCache(CACHE_KEY.userById(userId), updated, CACHE_TTL.users);
clearGlobalDataCache(CACHE_KEY.users);
return updated;
}, [endpoints.users]);
// Only called by Join Global Game flow to mirror a private user into global API schema. // Only called by Join Global Game flow to mirror a private user into global API schema.
const ensureGlobalUser = useCallback(async (userId, privateUser = null) => { const ensureGlobalUser = useCallback(
if (!userId || !isGlobalApiReady()) return null; async (userId, privateUser = null) => {
if (!userId || !isGlobalApiReady()) return null;
const seed = buildGlobalUserPayload(userId, privateUser); const seed = buildGlobalUserPayload(userId, privateUser);
// Primary lookup by deterministic username bridge. // Primary lookup by deterministic username bridge.
const users = await getUsers(); const users = await getUsers();
const bySyncedUsername = users.find( const bySyncedUsername = users.find(
(user) => String(user.UserUsername) === String(seed.UserUsername), (user) => String(user.UserUsername) === String(seed.UserUsername),
); );
if (bySyncedUsername) return bySyncedUsername; if (bySyncedUsername) return bySyncedUsername;
const existing = await getUser(userId); const existing = await getUser(userId);
if (existing) return existing; if (existing) return existing;
const created = await createUser(seed); const created = await createUser(seed);
if (created) return created; if (created) return created;
// Final fallback: check list endpoint again after attempted creation. // Final fallback: check list endpoint again after attempted creation.
const refreshed = await getUsers({forceRefresh: true}); const refreshed = await getUsers({ forceRefresh: true });
return ( return (
refreshed.find((user) => String(user.UserUsername) === String(seed.UserUsername)) || refreshed.find(
refreshed.find((user) => String(user.UserID) === String(userId)) || (user) => String(user.UserUsername) === String(seed.UserUsername),
null ) ||
); refreshed.find((user) => String(user.UserID) === String(userId)) ||
}, [createUser, getUser, getUsers]); null
);
},
[createUser, getUser, getUsers],
);
// Return -------------------------------- // Return --------------------------------