142 lines
3.8 KiB
TypeScript
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>
|
|
);
|
|
}
|