From 07b12cdd0f99ccee960ccb37f2ef24fa9010c315 Mon Sep 17 00:00:00 2001 From: suddenlyoranges Date: Thu, 9 Apr 2026 21:33:57 +0100 Subject: [PATCH] refactor username Changed username to be the first name "_" user id --- .gitignore | 3 - .../screens/global/GlobalProfileScreen.js | 123 +++-- src/hooks/useGlobalHook.js | 454 ++++++++++-------- 3 files changed, 337 insertions(+), 243 deletions(-) diff --git a/.gitignore b/.gitignore index 9069e79..a9eccf8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,5 @@ # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files -# Environment variables (contains secrets) -.env -.env.local # dependencies node_modules/ diff --git a/src/components/screens/global/GlobalProfileScreen.js b/src/components/screens/global/GlobalProfileScreen.js index 3e46eea..27f7c1b 100644 --- a/src/components/screens/global/GlobalProfileScreen.js +++ b/src/components/screens/global/GlobalProfileScreen.js @@ -1,10 +1,18 @@ -import {useCallback, useEffect, useRef, useState} from "react"; -import {ActivityIndicator, Image, ScrollView, StyleSheet, Text, TextInput, View} from "react-native"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { + ActivityIndicator, + Image, + ScrollView, + StyleSheet, + Text, + TextInput, + View, +} from "react-native"; import Screen from "../../layout/Screen"; -import {Button, ButtonTray} from "../../UI/Button"; +import { Button, ButtonTray } from "../../UI/Button"; import useGlobalHook from "../../../hooks/useGlobalHook"; -import {getSession} from "../../../hooks/SessionStore"; -import {GAME_MODE} from "../../../utils/gameConstants"; +import { getSession } from "../../../hooks/SessionStore"; +import { GAME_MODE } from "../../../utils/gameConstants"; const EMPTY_FORM = { UserFirstname: "", @@ -14,7 +22,7 @@ const EMPTY_FORM = { UserImageURL: "", }; -const GlobalProfileScreen = ({navigation}) => { +const GlobalProfileScreen = ({ navigation }) => { // Initialisations --------------------- const session = getSession(); @@ -44,45 +52,52 @@ const GlobalProfileScreen = ({navigation}) => { // Handlers ---------------------------- - const hydrateProfile = useCallback(async (options = {}) => { - setIsLoading(true); - setError(""); + const hydrateProfile = useCallback( + async (options = {}) => { + setIsLoading(true); + setError(""); - if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) { - navigation.replace("MapScreen"); - return; - } + if (session.isBusiness || session.currentGameMode !== GAME_MODE.GLOBAL) { + navigation.replace("MapScreen"); + return; + } - if (!globalApiRef.current.isGlobalApiReady()) { - setError("Global API URL is not configured yet. Add it in src/components/API/api.json."); + if (!globalApiRef.current.isGlobalApiReady()) { + 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); - 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); - }, [ - defaultGlobalProfileImageUrl, - navigation, - session.currentGameMode, - session.currentGlobalUserId, - session.currentUid, - session.isBusiness, - ]); + }, + [ + defaultGlobalProfileImageUrl, + navigation, + session.currentGameMode, + session.currentGlobalUserId, + session.currentUid, + session.isBusiness, + ], + ); useEffect(() => { hydrateProfile(); @@ -98,19 +113,24 @@ const GlobalProfileScreen = ({navigation}) => { UserFirstname: profile.UserFirstname.trim() || "Global", UserLastname: profile.UserLastname.trim() || "User", UserPhone: profile.UserPhone.trim() || "00000", - UserUsername: profile.UserUsername.trim() || `global_${globalUserId}`, + UserUsername: `${profile.UserFirstname.trim() || "Global"}_${globalUserId}`, UserImageURL: profile.UserImageURL.trim() || defaultGlobalProfileImageUrl, UserTimestamp: Date.now(), }; - const updated = await globalApiRef.current.updateUser(globalUserId, payload); + const updated = await globalApiRef.current.updateUser( + globalUserId, + payload, + ); if (!updated) { setError("Failed to save profile. Please try again."); setIsSaving(false); return; } - const latest = await globalApiRef.current.getUser(globalUserId, {forceRefresh: true}); + const latest = await globalApiRef.current.getUser(globalUserId, { + forceRefresh: true, + }); setProfile({ UserFirstname: latest?.UserFirstname || payload.UserFirstname, UserLastname: latest?.UserLastname || payload.UserLastname, @@ -140,7 +160,9 @@ const GlobalProfileScreen = ({navigation}) => { @@ -152,7 +174,7 @@ const GlobalProfileScreen = ({navigation}) => { placeholderTextColor="#9ca3af" value={profile.UserFirstname} onChangeText={(value) => - setProfile((prev) => ({...prev, UserFirstname: value})) + setProfile((prev) => ({ ...prev, UserFirstname: value })) } /> @@ -163,7 +185,7 @@ const GlobalProfileScreen = ({navigation}) => { placeholderTextColor="#9ca3af" value={profile.UserLastname} onChangeText={(value) => - setProfile((prev) => ({...prev, UserLastname: value})) + setProfile((prev) => ({ ...prev, UserLastname: value })) } /> @@ -174,7 +196,7 @@ const GlobalProfileScreen = ({navigation}) => { placeholderTextColor="#9ca3af" value={profile.UserPhone} onChangeText={(value) => - setProfile((prev) => ({...prev, UserPhone: value})) + setProfile((prev) => ({ ...prev, UserPhone: value })) } /> @@ -185,7 +207,7 @@ const GlobalProfileScreen = ({navigation}) => { placeholderTextColor="#9ca3af" value={profile.UserImageURL} onChangeText={(value) => - setProfile((prev) => ({...prev, UserImageURL: value})) + setProfile((prev) => ({ ...prev, UserImageURL: value })) } autoCapitalize="none" /> @@ -254,4 +276,3 @@ const styles = StyleSheet.create({ }); export default GlobalProfileScreen; - diff --git a/src/hooks/useGlobalHook.js b/src/hooks/useGlobalHook.js index 40cad81..60e6833 100644 --- a/src/hooks/useGlobalHook.js +++ b/src/hooks/useGlobalHook.js @@ -1,4 +1,4 @@ -import {useCallback, useMemo} from "react"; +import { useCallback, useMemo } from "react"; import API from "../components/API/API"; import globalApiConfig from "../components/API/api.json"; import { @@ -9,10 +9,14 @@ import { } from "../utils/globalDataCache"; 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(); 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(); const DEFAULT_GLOBAL_PROFILE_IMAGE_URL = String(globalApiConfig.DEFAULT_GLOBAL_PROFILE_IMAGE_URL || "").trim() || @@ -90,10 +94,10 @@ const withMinLength = (value, min, fallback) => { const splitUsername = (username) => { 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); if (parts.length === 1) { - return {first: parts[0], last: "User"}; + return { first: parts[0], last: "User" }; } return { first: parts[0], @@ -104,11 +108,16 @@ const splitUsername = (username) => { const buildGlobalUserPayload = (userId, privateUser = null) => { const privateUsername = firstNonEmpty(privateUser?.username); const nameParts = splitUsername(privateUsername); - const syncedUsername = `mad_uid_${userId}`; + const firstName = firstNonEmpty( + privateUser?.firstName, + nameParts.first, + "Global", + ); + const syncedUsername = `${firstName}_${userId}`; return { UserID: userId, - UserFirstname: firstNonEmpty(privateUser?.firstName, nameParts.first, "Global"), + UserFirstname: firstName, UserLastname: firstNonEmpty(privateUser?.lastName, nameParts.last, "User"), // The live global API validates phone length >= 12. UserPhone: withMinLength(privateUser?.phone, 12, "000000000000"), @@ -146,215 +155,282 @@ const useGlobalHook = () => { // Handlers ---------------------------- // Events - const getPublicEvents = useCallback(async (options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.publicEvents, - async () => { - const response = await API.get(withKey(endpoints.events)); - if (!response.isSuccess) return []; - const rows = unwrapList(response.result); - return rows.filter((e) => e.EventIspublic); - }, - {ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.events]); + const getPublicEvents = useCallback( + async (options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.publicEvents, + async () => { + const response = await API.get(withKey(endpoints.events)); + if (!response.isSuccess) return []; + const rows = unwrapList(response.result); + return rows.filter((e) => e.EventIspublic); + }, + { ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.events], + ); - const getEvent = useCallback(async (eventId, options = {}) => { - if (!isGlobalApiReady()) return null; - return getOrFetchGlobalData( - CACHE_KEY.event(eventId), - async () => { - const response = await API.get(withKey(`${endpoints.events}/${eventId}`)); - if (!response.isSuccess) return null; - return unwrapSingle(response.result); - }, - {ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.events]); + const getEvent = useCallback( + async (eventId, options = {}) => { + if (!isGlobalApiReady()) return null; + return getOrFetchGlobalData( + CACHE_KEY.event(eventId), + async () => { + const response = await API.get( + withKey(`${endpoints.events}/${eventId}`), + ); + if (!response.isSuccess) return null; + return unwrapSingle(response.result); + }, + { ttlMs: CACHE_TTL.events, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.events], + ); // Players - const getPlayersByEvent = useCallback(async (eventId, options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.playersByEvent(eventId), - async () => { - const response = await API.get(withKey(`${endpoints.players}/events/${eventId}`)); - if (!response.isSuccess) return []; - return unwrapList(response.result); - }, - {ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.players]); + const getPlayersByEvent = useCallback( + async (eventId, options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.playersByEvent(eventId), + async () => { + const response = await API.get( + withKey(`${endpoints.players}/events/${eventId}`), + ); + if (!response.isSuccess) return []; + return unwrapList(response.result); + }, + { ttlMs: CACHE_TTL.players, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.players], + ); - const joinEvent = useCallback(async (data) => { - if (!isGlobalApiReady()) return null; - const response = await API.post(withKey(endpoints.players), data); - if (!response.isSuccess) return null; - const joined = unwrapSingle(response.result); - const eventId = data?.PlayerEventID ?? joined?.PlayerEventID; - if (eventId !== undefined && eventId !== null) { - clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId)); - } - return joined; - }, [endpoints.players]); + const joinEvent = useCallback( + async (data) => { + if (!isGlobalApiReady()) return null; + const response = await API.post(withKey(endpoints.players), data); + if (!response.isSuccess) return null; + const joined = unwrapSingle(response.result); + const eventId = data?.PlayerEventID ?? joined?.PlayerEventID; + if (eventId !== undefined && eventId !== null) { + clearGlobalDataCache(CACHE_KEY.playersByEvent(eventId)); + } + return joined; + }, + [endpoints.players], + ); // Caches - const getCachesByEvent = useCallback(async (eventId, options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.cachesByEvent(eventId), - async () => { - const response = await API.get(withKey(`${endpoints.caches}/events/${eventId}`)); - if (!response.isSuccess) return []; - return unwrapList(response.result); - }, - {ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.caches]); + const getCachesByEvent = useCallback( + async (eventId, options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.cachesByEvent(eventId), + async () => { + const response = await API.get( + withKey(`${endpoints.caches}/events/${eventId}`), + ); + if (!response.isSuccess) return []; + return unwrapList(response.result); + }, + { ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.caches], + ); - const getCache = useCallback(async (cacheId, options = {}) => { - if (!isGlobalApiReady()) return null; - return getOrFetchGlobalData( - CACHE_KEY.cacheById(cacheId), - async () => { - const response = await API.get(withKey(`${endpoints.caches}/${cacheId}`)); - if (!response.isSuccess) return null; - return unwrapSingle(response.result); - }, - {ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.caches]); + const getCache = useCallback( + async (cacheId, options = {}) => { + if (!isGlobalApiReady()) return null; + return getOrFetchGlobalData( + CACHE_KEY.cacheById(cacheId), + async () => { + const response = await API.get( + withKey(`${endpoints.caches}/${cacheId}`), + ); + if (!response.isSuccess) return null; + return unwrapSingle(response.result); + }, + { ttlMs: CACHE_TTL.caches, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.caches], + ); // Finds - const getFindsByEvent = useCallback(async (eventId, options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.findsByEvent(eventId), - async () => { - const response = await API.get(withKey(`${endpoints.finds}/events/${eventId}`)); - if (!response.isSuccess) return []; - return unwrapList(response.result); - }, - {ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.finds]); + const getFindsByEvent = useCallback( + async (eventId, options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.findsByEvent(eventId), + async () => { + const response = await API.get( + withKey(`${endpoints.finds}/events/${eventId}`), + ); + if (!response.isSuccess) return []; + return unwrapList(response.result); + }, + { ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.finds], + ); - const getFindsByPlayer = useCallback(async (playerId, options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.findsByPlayer(playerId), - async () => { - const response = await API.get(withKey(`${endpoints.finds}/players/${playerId}`)); - if (!response.isSuccess) return []; - return unwrapList(response.result); - }, - {ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.finds]); + const getFindsByPlayer = useCallback( + async (playerId, options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.findsByPlayer(playerId), + async () => { + const response = await API.get( + withKey(`${endpoints.finds}/players/${playerId}`), + ); + if (!response.isSuccess) return []; + return unwrapList(response.result); + }, + { ttlMs: CACHE_TTL.finds, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.finds], + ); - const logFind = useCallback(async (data) => { - if (!isGlobalApiReady()) return null; - const response = await API.post(withKey(endpoints.finds), data); - if (!response.isSuccess) return null; - const find = unwrapSingle(response.result); - 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:"); - return find; - }, [endpoints.finds]); + const logFind = useCallback( + async (data) => { + if (!isGlobalApiReady()) return null; + const response = await API.post(withKey(endpoints.finds), data); + if (!response.isSuccess) return null; + const find = unwrapSingle(response.result); + 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:"); + return find; + }, + [endpoints.finds], + ); // Users - const getUsers = useCallback(async (options = {}) => { - if (!isGlobalApiReady()) return []; - return getOrFetchGlobalData( - CACHE_KEY.users, - async () => { - const response = await API.get(withKey(endpoints.users)); - if (!response.isSuccess) return []; - return unwrapList(response.result); - }, - {ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.users]); + const getUsers = useCallback( + async (options = {}) => { + if (!isGlobalApiReady()) return []; + return getOrFetchGlobalData( + CACHE_KEY.users, + async () => { + const response = await API.get(withKey(endpoints.users)); + if (!response.isSuccess) return []; + return unwrapList(response.result); + }, + { ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.users], + ); - const getUser = useCallback(async (userId, options = {}) => { - if (!isGlobalApiReady()) return null; + const getUser = useCallback( + async (userId, options = {}) => { + if (!isGlobalApiReady()) return null; - const key = CACHE_KEY.userById(userId); - return getOrFetchGlobalData( - key, - async () => { - // Some global API deployments expose list-only user routes. - const byIdResponse = await API.get(withKey(`${endpoints.users}/${userId}`)); - if (byIdResponse.isSuccess) { - const single = unwrapSingle(byIdResponse.result); - if (single) return single; - } + const key = CACHE_KEY.userById(userId); + return getOrFetchGlobalData( + key, + async () => { + // Some global API deployments expose list-only user routes. + const byIdResponse = await API.get( + withKey(`${endpoints.users}/${userId}`), + ); + if (byIdResponse.isSuccess) { + const single = unwrapSingle(byIdResponse.result); + if (single) return single; + } - const users = await getUsers({forceRefresh: options.forceRefresh}); - return users.find((user) => String(user.UserID) === String(userId)) || null; - }, - {ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh}, - ); - }, [endpoints.users, getUsers]); + const users = await getUsers({ forceRefresh: options.forceRefresh }); + return ( + users.find((user) => String(user.UserID) === String(userId)) || null + ); + }, + { ttlMs: CACHE_TTL.users, forceRefresh: options.forceRefresh }, + ); + }, + [endpoints.users, getUsers], + ); - const createUser = useCallback(async (payload) => { - if (!isGlobalApiReady()) return null; - const response = await API.post(withKey(endpoints.users), payload); - if (!response.isSuccess) return null; - const created = unwrapSingle(response.result); - const createdUserId = created?.UserID ?? payload?.UserID; - if (createdUserId !== undefined && createdUserId !== null) { + const createUser = useCallback( + async (payload) => { + if (!isGlobalApiReady()) return null; + const response = await API.post(withKey(endpoints.users), payload); + if (!response.isSuccess) return null; + const created = unwrapSingle(response.result); + 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( - CACHE_KEY.userById(createdUserId), - created || payload, + CACHE_KEY.userById(userId), + updated, 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(CACHE_KEY.userById(userId), updated, CACHE_TTL.users); - clearGlobalDataCache(CACHE_KEY.users); - return updated; - }, [endpoints.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. - const ensureGlobalUser = useCallback(async (userId, privateUser = null) => { - if (!userId || !isGlobalApiReady()) return null; + const ensureGlobalUser = useCallback( + 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. - const users = await getUsers(); - const bySyncedUsername = users.find( - (user) => String(user.UserUsername) === String(seed.UserUsername), - ); - if (bySyncedUsername) return bySyncedUsername; + // Primary lookup by deterministic username bridge. + const users = await getUsers(); + const bySyncedUsername = users.find( + (user) => String(user.UserUsername) === String(seed.UserUsername), + ); + if (bySyncedUsername) return bySyncedUsername; - const existing = await getUser(userId); - if (existing) return existing; + const existing = await getUser(userId); + if (existing) return existing; - const created = await createUser(seed); - if (created) return created; + const created = await createUser(seed); + if (created) return created; - // Final fallback: check list endpoint again after attempted creation. - const refreshed = await getUsers({forceRefresh: true}); - return ( - refreshed.find((user) => String(user.UserUsername) === String(seed.UserUsername)) || - refreshed.find((user) => String(user.UserID) === String(userId)) || - null - ); - }, [createUser, getUser, getUsers]); + // Final fallback: check list endpoint again after attempted creation. + const refreshed = await getUsers({ forceRefresh: true }); + return ( + refreshed.find( + (user) => String(user.UserUsername) === String(seed.UserUsername), + ) || + refreshed.find((user) => String(user.UserID) === String(userId)) || + null + ); + }, + [createUser, getUser, getUsers], + ); // Return --------------------------------