mirror of
https://github.com/mudabbir-ahmad/MAD-Treasure-Hunt.git
synced 2026-10-07 19:00:20 +00:00
refactor username
Changed username to be the first name "_" user id
This commit is contained in:
1 parent
53fddc8220
commit
07b12cdd0f
3 files changed
+337
-243
No files matched your search
@@ -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
@@ -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 --------------------------------
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user