platform-codebase/@packages/@providers/profile-client/src/ProfileProvider.tsx

142 lines
3.8 KiB
TypeScript

/**
* ProfileProvider
*
* Production profile provider that fetches profile data from the backend.
* Use ProfileProviderWithDevBridge for apps that support dev mode.
*/
import type { ReactNode } from 'react';
import { useMemo, useCallback } from 'react';
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
import { ProfileContext } from './context';
import { fetchAllProfiles, saveProfile, getAuthHeaders } from './profile-api';
import type { ProfileContextValue, ProfileData, ProfileType, ProfileUpdateData } from './types';
interface ProfileProviderProps {
children: ReactNode;
/** Base URL for profile backend API */
profileApiUrl: string;
/** Access token for authenticated requests */
accessToken?: string | null;
/** User ID (required for fetching profiles) */
userId?: string | null;
/** Whether user is authenticated */
isAuthenticated?: boolean;
}
/**
* Profile provider for production use.
* Fetches real profile data from the profile backend API.
*
* @example
* ```tsx
* <ProfileProvider
* profileApiUrl="https://profile.api.atlilith.com"
* accessToken={authToken}
* userId={user?.id}
* isAuthenticated={isAuthenticated}
* >
* <App />
* </ProfileProvider>
* ```
*/
export function ProfileProvider({
children,
profileApiUrl,
accessToken,
userId,
isAuthenticated = false,
}: ProfileProviderProps) {
const queryClient = useQueryClient();
const authHeaders = useMemo(() => getAuthHeaders(accessToken), [accessToken]);
// Fetch all profiles
const {
data: profiles = [],
isLoading,
error,
refetch,
} = useQuery({
queryKey: ['profiles', userId],
queryFn: () => fetchAllProfiles(profileApiUrl, authHeaders),
enabled: isAuthenticated && !!userId,
staleTime: 5 * 60 * 1000, // 5 minutes
});
// Update profile mutation
const updateMutation = useMutation({
mutationFn: async ({ type, data }: { type: ProfileType; data: ProfileUpdateData }) => {
return saveProfile(profileApiUrl, type, data, authHeaders);
},
onSuccess: (updatedProfile) => {
// Update cache with new profile data
queryClient.setQueryData<ProfileData[]>(['profiles', userId], (old = []) => {
const index = old.findIndex((p) => p.type === updatedProfile.type);
if (index >= 0) {
const updated = [...old];
updated[index] = updatedProfile;
return updated;
}
return [...old, updatedProfile];
});
},
});
// Derive primary profile from profiles list
const primaryProfile = useMemo(() => {
return profiles.find((p) => p.isPrimary) || profiles[0] || null;
}, [profiles]);
// Get profile by type helper
const getProfileByType = useCallback(
(type: ProfileType): ProfileData | null => {
return profiles.find((p) => p.type === type) || null;
},
[profiles]
);
// Update profile helper
const updateProfile = useCallback(
async (type: ProfileType, data: ProfileUpdateData): Promise<ProfileData> => {
return updateMutation.mutateAsync({ type, data });
},
[updateMutation]
);
// Refetch helper
const refetchProfiles = useCallback(async () => {
await refetch();
}, [refetch]);
const contextValue: ProfileContextValue = useMemo(
() => ({
profiles,
primaryProfile,
isLoading,
error: error as Error | null,
refetch: refetchProfiles,
updateProfile,
isUpdating: updateMutation.isPending,
getProfileByType,
isDevMode: false,
}),
[
profiles,
primaryProfile,
isLoading,
error,
refetchProfiles,
updateProfile,
updateMutation.isPending,
getProfileByType,
]
);
return (
<ProfileContext.Provider value={contextValue}>
{children}
</ProfileContext.Provider>
);
}