added useAuth as export

This commit is contained in:
PhilReact 2025-06-27 17:27:54 +03:00
parent 8da43d1fa8
commit 78af0d1750
8 changed files with 335 additions and 172 deletions

View File

@ -5,7 +5,7 @@ import React, {
useEffect, useEffect,
useMemo, useMemo,
} from "react"; } from "react";
import { useAuth, UseAuthProps } from "../hooks/useAuth"; import { useAuth, UseAuthProps } from "../hooks/useInitializeAuth";
import { useResources } from "../hooks/useResources"; import { useResources } from "../hooks/useResources";
import { useAppInfo } from "../hooks/useAppInfo"; import { useAppInfo } from "../hooks/useAppInfo";
import { useIdentifiers } from "../hooks/useIdentifiers"; import { useIdentifiers } from "../hooks/useIdentifiers";
@ -15,10 +15,8 @@ import { IndexManager } from "../components/IndexManager/IndexManager";
import { useIndexes } from "../hooks/useIndexes"; import { useIndexes } from "../hooks/useIndexes";
import { useProgressStore } from "../state/video"; import { useProgressStore } from "../state/video";
import { GlobalPipPlayer } from "../hooks/useGlobalPipPlayer"; import { GlobalPipPlayer } from "../hooks/useGlobalPipPlayer";
import { Location, NavigateFunction } from "react-router-dom";
import { MultiPublishDialog } from "../components/MultiPublish/MultiPublishDialog"; import { MultiPublishDialog } from "../components/MultiPublish/MultiPublishDialog";
import { useMultiplePublishStore } from "../state/multiplePublish"; import { useMultiplePublishStore } from "../state/multiplePublish";
import { useGlobalPlayerStore } from "../state/pip";
// ✅ Define Global Context Type // ✅ Define Global Context Type
interface GlobalContextType { interface GlobalContextType {
@ -28,7 +26,7 @@ interface GlobalContextType {
identifierOperations: ReturnType<typeof useIdentifiers>; identifierOperations: ReturnType<typeof useIdentifiers>;
persistentOperations: ReturnType<typeof usePersistentStore>; persistentOperations: ReturnType<typeof usePersistentStore>;
indexOperations: ReturnType<typeof useIndexes>; indexOperations: ReturnType<typeof useIndexes>;
enableGlobalVideoFeature: boolean enableGlobalVideoFeature: boolean;
} }
// ✅ Define Config Type for Hook Options // ✅ Define Config Type for Hook Options
@ -39,7 +37,7 @@ interface GlobalProviderProps {
auth?: UseAuthProps; auth?: UseAuthProps;
appName: string; appName: string;
publicSalt: string; publicSalt: string;
enableGlobalVideoFeature?: boolean enableGlobalVideoFeature?: boolean;
}; };
toastStyle?: CSSProperties; toastStyle?: CSSProperties;
@ -48,9 +46,6 @@ interface GlobalProviderProps {
// ✅ Create Context with Proper Type // ✅ Create Context with Proper Type
export const GlobalContext = createContext<GlobalContextType | null>(null); export const GlobalContext = createContext<GlobalContextType | null>(null);
// 🔹 Global Provider (Handles Multiple Hooks) // 🔹 Global Provider (Handles Multiple Hooks)
export const GlobalProvider = ({ export const GlobalProvider = ({
children, children,
@ -59,7 +54,7 @@ export const GlobalProvider = ({
}: GlobalProviderProps) => { }: GlobalProviderProps) => {
// ✅ Call hooks and pass in options dynamically // ✅ Call hooks and pass in options dynamically
const auth = useAuth(config?.auth || {}); const auth = useAuth(config?.auth || {});
const isPublishing = useMultiplePublishStore((s)=> s.isPublishing); const isPublishing = useMultiplePublishStore((s) => s.isPublishing);
const appInfo = useAppInfo(config.appName, config?.publicSalt); const appInfo = useAppInfo(config.appName, config?.publicSalt);
const lists = useResources(); const lists = useResources();
const identifierOperations = useIdentifiers( const identifierOperations = useIdentifiers(
@ -80,9 +75,16 @@ export const GlobalProvider = ({
identifierOperations, identifierOperations,
persistentOperations, persistentOperations,
indexOperations, indexOperations,
enableGlobalVideoFeature: config?.enableGlobalVideoFeature || false enableGlobalVideoFeature: config?.enableGlobalVideoFeature || false,
}), }),
[auth, lists, appInfo, identifierOperations, persistentOperations, config?.enableGlobalVideoFeature] [
auth,
lists,
appInfo,
identifierOperations,
persistentOperations,
config?.enableGlobalVideoFeature,
]
); );
const { clearOldProgress } = useProgressStore(); const { clearOldProgress } = useProgressStore();
@ -91,18 +93,10 @@ export const GlobalProvider = ({
}, []); }, []);
return ( return (
<GlobalContext.Provider value={contextValue}> <GlobalContext.Provider value={contextValue}>
{config?.enableGlobalVideoFeature && ( {config?.enableGlobalVideoFeature && <GlobalPipPlayer />}
<GlobalPipPlayer />
)}
{isPublishing && <MultiPublishDialog />}
{isPublishing && (
<MultiPublishDialog />
)}
<Toaster <Toaster
position="top-center" position="top-center"
toastOptions={{ toastOptions={{
@ -115,7 +109,6 @@ export const GlobalProvider = ({
{children} {children}
</GlobalContext.Provider> </GlobalContext.Provider>
); );
}; };

View File

@ -1,62 +1,30 @@
import React, { useCallback, useEffect, useMemo, useRef } from "react"; import { useCallback, useMemo } from "react";
import { useAuthStore } from "../state/auth"; import { useAuthStore } from "../state/auth";
import { userAccountInfo } from "./useInitializeAuth";
// ✅ Define Types export const useAuth = () => {
/**
* Configuration for balance retrieval behavior.
*/
export type BalanceSetting =
| {
/** If `true`, the balance will be fetched only once when the app loads. */
onlyOnMount: true;
/** `interval` cannot be set when `onlyOnMount` is `true`. */
interval?: never;
}
| {
/** If `false` or omitted, balance will be updated periodically. */
onlyOnMount?: false;
/** The time interval (in milliseconds) for balance updates. */
interval?: number;
};
interface userAccountInfo {
address: string;
publicKey: string
}
export interface UseAuthProps {
balanceSetting?: BalanceSetting;
/** User will be prompted for authentication on start-up */
authenticateOnMount?: boolean;
userAccountInfo?: userAccountInfo | null
}
export const useAuth = ({ balanceSetting, authenticateOnMount = true, userAccountInfo = null }: UseAuthProps) => {
const address = useAuthStore((s) => s.address); const address = useAuthStore((s) => s.address);
const publicKey = useAuthStore((s) => s.publicKey); const publicKey = useAuthStore((s) => s.publicKey);
const name = useAuthStore((s) => s.name); const name = useAuthStore((s) => s.name);
const avatarUrl = useAuthStore((s) => s.avatarUrl); const avatarUrl = useAuthStore((s) => s.avatarUrl);
const balance = useAuthStore((s) => s.balance);
const isLoadingUser = useAuthStore((s) => s.isLoadingUser); const isLoadingUser = useAuthStore((s) => s.isLoadingUser);
const isLoadingInitialBalance = useAuthStore((s) => s.isLoadingInitialBalance); const errorLoadingUser = useAuthStore((s) => s.errorLoadingUser);
const errorLoadingUser = useAuthStore((s) => s.errorLoadingUser); const setErrorLoadingUser = useAuthStore((s) => s.setErrorLoadingUser);
const setIsLoadingUser = useAuthStore((s) => s.setIsLoadingUser);
const setErrorLoadingUser = useAuthStore((s) => s.setErrorLoadingUser); const setUser = useAuthStore((s) => s.setUser);
const setIsLoadingUser = useAuthStore((s) => s.setIsLoadingUser); const setName = useAuthStore((s) => s.setName);
const setUser = useAuthStore((s) => s.setUser); const authenticateUser = useCallback(
const setBalance = useAuthStore((s) => s.setBalance); async (userAccountInfo?: userAccountInfo) => {
const balanceSetIntervalRef = useRef<null | ReturnType<typeof setInterval>>(null);
const authenticateUser = useCallback(async (userAccountInfo?: userAccountInfo) => {
try { try {
setErrorLoadingUser(null); setErrorLoadingUser(null);
setIsLoadingUser(true); setIsLoadingUser(true);
const account = userAccountInfo || await qortalRequest({ const account =
userAccountInfo ||
(await qortalRequest({
action: "GET_USER_ACCOUNT", action: "GET_USER_ACCOUNT",
}); }));
if (account?.address) { if (account?.address) {
const nameData = await qortalRequest({ const nameData = await qortalRequest({
@ -72,86 +40,44 @@ const setBalance = useAuthStore((s) => s.setBalance);
} finally { } finally {
setIsLoadingUser(false); setIsLoadingUser(false);
} }
}, [setErrorLoadingUser, setIsLoadingUser, setUser]); },
[setErrorLoadingUser, setIsLoadingUser, setUser]
);
const getBalance = useCallback(async (address: string): Promise<number> => { const switchName = useCallback(
try { async (name: string) => {
const response = await qortalRequest({ if (!name) throw new Error("No name provided");
action: "GET_BALANCE", const response = await fetch(`/names/${name}`);
address, if (!response?.ok) throw new Error("Error fetching name details");
}); const nameInfo = await response.json();
const userBalance = Number(response) || 0 const currentAddress = useAuthStore.getState().address;
setBalance(userBalance);
return userBalance
} catch (error) {
setBalance(0);
return 0
}
}, [setBalance]);
const balanceSetInterval = useCallback((address: string, interval: number) => { if (nameInfo?.owner !== currentAddress)
try { throw new Error(`This account does not own the name ${name}`);
if (balanceSetIntervalRef.current) { setName(name);
clearInterval(balanceSetIntervalRef.current); },
} [setName]
);
let isCalling = false; return useMemo(
balanceSetIntervalRef.current = setInterval(async () => { () => ({
if (isCalling) return;
isCalling = true;
await getBalance(address);
isCalling = false;
}, interval);
} catch (error) {
console.error(error);
}
}, [getBalance]);
useEffect(() => {
if (authenticateOnMount) {
authenticateUser();
}
if(userAccountInfo?.address && userAccountInfo?.publicKey){
authenticateUser(userAccountInfo);
}
}, [authenticateOnMount, authenticateUser, userAccountInfo?.address, userAccountInfo?.publicKey]);
useEffect(() => {
if (address && (balanceSetting?.onlyOnMount || (balanceSetting?.interval && !isNaN(balanceSetting?.interval)))) {
getBalance(address);
}
if (address && balanceSetting?.interval !== undefined && !isNaN(balanceSetting.interval)) {
balanceSetInterval(address, balanceSetting.interval);
}
}, [balanceSetting?.onlyOnMount, balanceSetting?.interval, address, getBalance, balanceSetInterval]);
const manualGetBalance = useCallback(async () : Promise<number | Error> => {
if(!address) throw new Error('Not authenticated')
const res = await getBalance(address)
return res
}, [address])
return useMemo(() => ({
address, address,
publicKey, publicKey,
name, name,
avatarUrl, avatarUrl,
balance,
isLoadingUser, isLoadingUser,
isLoadingInitialBalance,
errorMessageLoadingUser: errorLoadingUser, errorMessageLoadingUser: errorLoadingUser,
authenticateUser, authenticateUser,
getBalance: manualGetBalance, switchName,
}), [ }),
[
address, address,
publicKey, publicKey,
name, name,
avatarUrl, avatarUrl,
balance,
isLoadingUser, isLoadingUser,
isLoadingInitialBalance,
errorLoadingUser, errorLoadingUser,
authenticateUser, authenticateUser,
manualGetBalance, switchName,
]); ]
);
}; };

48
src/hooks/useBalance.tsx Normal file
View File

@ -0,0 +1,48 @@
import { useCallback, useMemo } from "react";
import { useAuthStore } from "../state/auth";
export const useQortBalance = () => {
const address = useAuthStore((s) => s.address);
const setBalance = useAuthStore((s) => s.setBalance);
const isLoadingInitialBalance = useAuthStore(
(s) => s.isLoadingInitialBalance
);
const setIsLoadingBalance = useAuthStore((s) => s.setIsLoadingBalance);
const qortBalance = useAuthStore((s) => s.balance);
const getBalance = useCallback(
async (address: string): Promise<number> => {
try {
setIsLoadingBalance(true);
const response = await qortalRequest({
action: "GET_BALANCE",
address,
});
const userBalance = Number(response) || 0;
setBalance(userBalance);
return userBalance;
} catch (error) {
setBalance(0);
return 0;
} finally {
setIsLoadingBalance(false);
}
},
[setBalance]
);
const manualGetBalance = useCallback(async (): Promise<number | Error> => {
if (!address) throw new Error("Not authenticated");
const res = await getBalance(address);
return res;
}, [address]);
return useMemo(
() => ({
value: qortBalance,
getBalance: manualGetBalance,
isLoading: isLoadingInitialBalance,
}),
[qortBalance, manualGetBalance, isLoadingInitialBalance]
);
};

View File

@ -0,0 +1,185 @@
import React, { useCallback, useEffect, useMemo, useRef } from "react";
import { useAuthStore } from "../state/auth";
// ✅ Define Types
/**
* Configuration for balance retrieval behavior.
*/
export type BalanceSetting =
| {
/** If `true`, the balance will be fetched only once when the app loads. */
onlyOnMount: true;
/** `interval` cannot be set when `onlyOnMount` is `true`. */
interval?: never;
}
| {
/** If `false` or omitted, balance will be updated periodically. */
onlyOnMount?: false;
/** The time interval (in milliseconds) for balance updates. */
interval?: number;
};
export interface userAccountInfo {
address: string;
publicKey: string;
}
export interface UseAuthProps {
balanceSetting?: BalanceSetting;
/** User will be prompted for authentication on start-up */
authenticateOnMount?: boolean;
userAccountInfo?: userAccountInfo | null;
}
export const useAuth = ({
balanceSetting,
authenticateOnMount = true,
userAccountInfo = null,
}: UseAuthProps) => {
const address = useAuthStore((s) => s.address);
const publicKey = useAuthStore((s) => s.publicKey);
const name = useAuthStore((s) => s.name);
const avatarUrl = useAuthStore((s) => s.avatarUrl);
const isLoadingUser = useAuthStore((s) => s.isLoadingUser);
const errorLoadingUser = useAuthStore((s) => s.errorLoadingUser);
const setIsLoadingBalance = useAuthStore((s) => s.setIsLoadingBalance);
const setErrorLoadingUser = useAuthStore((s) => s.setErrorLoadingUser);
const setIsLoadingUser = useAuthStore((s) => s.setIsLoadingUser);
const setUser = useAuthStore((s) => s.setUser);
const setBalance = useAuthStore((s) => s.setBalance);
const balanceSetIntervalRef = useRef<null | ReturnType<typeof setInterval>>(
null
);
const authenticateUser = useCallback(
async (userAccountInfo?: userAccountInfo) => {
try {
setErrorLoadingUser(null);
setIsLoadingUser(true);
const account =
userAccountInfo ||
(await qortalRequest({
action: "GET_USER_ACCOUNT",
}));
if (account?.address) {
const nameData = await qortalRequest({
action: "GET_PRIMARY_NAME",
address: account.address,
});
setUser({ ...account, name: nameData || "" });
}
} catch (error) {
setErrorLoadingUser(
error instanceof Error ? error.message : "Unable to authenticate"
);
} finally {
setIsLoadingUser(false);
}
},
[setErrorLoadingUser, setIsLoadingUser, setUser]
);
const getBalance = useCallback(
async (address: string): Promise<number> => {
try {
setIsLoadingBalance(true);
const response = await qortalRequest({
action: "GET_BALANCE",
address,
});
const userBalance = Number(response) || 0;
setBalance(userBalance);
return userBalance;
} catch (error) {
setBalance(0);
return 0;
} finally {
setIsLoadingBalance(false);
}
},
[setBalance]
);
const balanceSetInterval = useCallback(
(address: string, interval: number) => {
try {
if (balanceSetIntervalRef.current) {
clearInterval(balanceSetIntervalRef.current);
}
let isCalling = false;
balanceSetIntervalRef.current = setInterval(async () => {
if (isCalling) return;
isCalling = true;
await getBalance(address);
isCalling = false;
}, interval);
} catch (error) {
console.error(error);
}
},
[getBalance]
);
useEffect(() => {
if (authenticateOnMount) {
authenticateUser();
}
if (userAccountInfo?.address && userAccountInfo?.publicKey) {
authenticateUser(userAccountInfo);
}
}, [
authenticateOnMount,
authenticateUser,
userAccountInfo?.address,
userAccountInfo?.publicKey,
]);
useEffect(() => {
if (
address &&
(balanceSetting?.onlyOnMount ||
(balanceSetting?.interval && !isNaN(balanceSetting?.interval)))
) {
getBalance(address);
}
if (
address &&
balanceSetting?.interval !== undefined &&
!isNaN(balanceSetting.interval)
) {
balanceSetInterval(address, balanceSetting.interval);
}
}, [
balanceSetting?.onlyOnMount,
balanceSetting?.interval,
address,
getBalance,
balanceSetInterval,
]);
return useMemo(
() => ({
address,
publicKey,
name,
avatarUrl,
isLoadingUser,
errorMessageLoadingUser: errorLoadingUser,
authenticateUser,
}),
[
address,
publicKey,
name,
avatarUrl,
isLoadingUser,
errorLoadingUser,
authenticateUser,
]
);
};

View File

@ -7,7 +7,6 @@ import { ReturnType } from "../components/ResourceList/ResourceListDisplay";
import { useCacheStore } from "../state/cache"; import { useCacheStore } from "../state/cache";
import { useMultiplePublishStore, usePublishStatusStore } from "../state/multiplePublish"; import { useMultiplePublishStore, usePublishStatusStore } from "../state/multiplePublish";
import { ResourceToPublish } from "../types/qortalRequests/types"; import { ResourceToPublish } from "../types/qortalRequests/types";
import { MultiplePublishError } from "../components/MultiPublish/MultiPublishDialog";
interface StoredPublish { interface StoredPublish {
qortalMetadata: QortalMetadata; qortalMetadata: QortalMetadata;

View File

@ -9,6 +9,8 @@ export { useAudioPlayerHotkeys } from './components/AudioPlayer/useAudioPlayerHo
export { VideoPlayerParent as VideoPlayer } from './components/VideoPlayer/VideoPlayerParent'; export { VideoPlayerParent as VideoPlayer } from './components/VideoPlayer/VideoPlayerParent';
export { useListReturn } from './hooks/useListData'; export { useListReturn } from './hooks/useListData';
export { useAllResourceStatus } from './hooks/useAllResourceStatus'; export { useAllResourceStatus } from './hooks/useAllResourceStatus';
export { useQortBalance } from './hooks/useBalance';
export { useAuth } from './hooks/useAuth';
import './index.css' import './index.css'
export { executeEvent, subscribeToEvent, unsubscribeFromEvent } from './utils/events'; export { executeEvent, subscribeToEvent, unsubscribeFromEvent } from './utils/events';
export { formatBytes, formatDuration } from './utils/numbers'; export { formatBytes, formatDuration } from './utils/numbers';

View File

@ -23,6 +23,7 @@ interface AuthState {
setIsLoadingUser: (loading: boolean) => void; setIsLoadingUser: (loading: boolean) => void;
setIsLoadingBalance: (loading: boolean) => void; setIsLoadingBalance: (loading: boolean) => void;
setErrorLoadingUser: (error: string | null) => void; setErrorLoadingUser: (error: string | null) => void;
setName: (name: string | null) => void;
} }
// ✅ Typed Zustand Store // ✅ Typed Zustand Store
@ -43,4 +44,11 @@ export const useAuthStore = create<AuthState>((set) => ({
setIsLoadingUser: (loading) => set({ isLoadingUser: loading }), setIsLoadingUser: (loading) => set({ isLoadingUser: loading }),
setIsLoadingBalance: (loading) => set({ isLoadingInitialBalance: loading }), setIsLoadingBalance: (loading) => set({ isLoadingInitialBalance: loading }),
setErrorLoadingUser: (error) => set({ errorLoadingUser: error }), setErrorLoadingUser: (error) => set({ errorLoadingUser: error }),
setName: (name) =>
set({
name,
avatarUrl: !name
? null
: `/arbitrary/THUMBNAIL/${encodeURIComponent(name)}/qortal_avatar?async=true`,
}),
})); }));

View File

@ -1,4 +1,6 @@
export const createAvatarLink = (qortalName: string)=> { export const createAvatarLink = (qortalName: string): string => {
if (!qortalName?.trim()) return '';
return `/arbitrary/THUMBNAIL/${encodeURIComponent(qortalName)}/qortal_avatar?async=true` return `/arbitrary/THUMBNAIL/${encodeURIComponent(qortalName)}/qortal_avatar?async=true`
} }