* react compiler * remove compilation mode 'all', it will use infer by default * feat(companion): add theme tokens, path aliases, and component improvements - Extend tailwind.config.js with Cal.com brand color tokens (cal.text, cal.bg, cal.border, cal.accent, cal.brand) - Create theme/colors.ts for JS usage where Tailwind classes can't be used - Configure path aliases in tsconfig.json (@/components, @/hooks, @/utils, etc.) - Add expo-image and expo-haptics dependencies - Enhance AppPressable with Reanimated animations (opacity + scale) and haptic feedback - Extract shared logic from BookingListItem into useBookingListItemData hook and BookingListItemParts - Extract shared logic from EventTypeListItem into useEventTypeListItemData hook and EventTypeListItemParts - Extract shared logic from AvailabilityListItem into AvailabilityListItemParts - Update all list item components to use new theme tokens instead of hardcoded hex values * fix react compiler non memo issue * feat(companion): migrate to path aliases and expo-image - Remove theme/colors.ts and theme/index.ts (defer dark mode to future PR) - Replace colors import with hardcoded hex values in components - Migrate all files to use path aliases (@/components/*, @/hooks/*, etc.) - Migrate SvgImage to use expo-image for better performance - Update tsconfig.json to remove @/theme/* alias * fix(companion): add path aliases to extension tsconfig Add baseUrl and paths configuration to tsconfig.extension.json to support path aliases in shared types used by the extension build.
201 lines
5.5 KiB
TypeScript
201 lines
5.5 KiB
TypeScript
/**
|
|
* React Query Context Provider
|
|
*
|
|
* This module sets up React Query with:
|
|
* - Optimized default configurations
|
|
* - Offline persistence support
|
|
* - Environment-based cache duration settings
|
|
* - Cross-platform compatibility (mobile + extension)
|
|
*/
|
|
|
|
import { onlineManager, QueryClient } from "@tanstack/react-query";
|
|
import { PersistQueryClientProvider } from "@tanstack/react-query-persist-client";
|
|
import React, { type ReactNode, useCallback, useEffect, useMemo, useState } from "react";
|
|
import { Platform } from "react-native";
|
|
import { CACHE_CONFIG } from "@/config/cache.config";
|
|
import { clearQueryCache, createQueryPersister } from "@/utils/queryPersister";
|
|
|
|
/**
|
|
* Create and configure the QueryClient instance
|
|
*/
|
|
const createQueryClient = (): QueryClient => {
|
|
return new QueryClient({
|
|
defaultOptions: {
|
|
queries: {
|
|
// How long data is considered fresh
|
|
staleTime: CACHE_CONFIG.defaultStaleTime,
|
|
|
|
// How long to keep unused data in cache
|
|
gcTime: CACHE_CONFIG.gcTime,
|
|
|
|
// Refetch behavior
|
|
refetchOnWindowFocus: CACHE_CONFIG.refetch.onWindowFocus,
|
|
refetchOnReconnect: CACHE_CONFIG.refetch.onReconnect,
|
|
refetchOnMount: CACHE_CONFIG.refetch.onMount,
|
|
|
|
// Retry configuration
|
|
retry: CACHE_CONFIG.retry.count,
|
|
retryDelay: CACHE_CONFIG.retry.delay,
|
|
|
|
// Network mode - always try to fetch, use cache as fallback
|
|
networkMode: "offlineFirst",
|
|
},
|
|
mutations: {
|
|
// Retry failed mutations
|
|
retry: 1,
|
|
retryDelay: 1000,
|
|
|
|
// Network mode for mutations
|
|
networkMode: "offlineFirst",
|
|
},
|
|
},
|
|
});
|
|
};
|
|
|
|
/**
|
|
* Props for the QueryProvider component
|
|
*/
|
|
interface QueryProviderProps {
|
|
children: ReactNode;
|
|
}
|
|
|
|
/**
|
|
* Context for exposing query utilities
|
|
*/
|
|
interface QueryContextValue {
|
|
/** Invalidate all queries and refetch */
|
|
invalidateAllQueries: () => Promise<void>;
|
|
/** Clear the persisted cache */
|
|
clearCache: () => Promise<void>;
|
|
/** Check if the app is online */
|
|
isOnline: boolean;
|
|
}
|
|
|
|
const QueryContext = React.createContext<QueryContextValue | undefined>(undefined);
|
|
|
|
/**
|
|
* QueryProvider component that wraps the app with React Query functionality
|
|
*
|
|
* Features:
|
|
* - Automatic cache persistence to device storage
|
|
* - Online/offline detection
|
|
* - Configurable cache durations via environment variables
|
|
*/
|
|
export function QueryProvider({ children }: QueryProviderProps) {
|
|
// Create QueryClient instance (stable reference)
|
|
const [queryClient] = useState(() => createQueryClient());
|
|
|
|
// Create persister instance (stable reference)
|
|
const [persister] = useState(() => createQueryPersister());
|
|
|
|
// Track online status
|
|
const [isOnline, setIsOnline] = useState(true);
|
|
|
|
// Setup online/offline detection
|
|
useEffect(() => {
|
|
// For web/extension
|
|
if (Platform.OS === "web") {
|
|
const handleOnline = () => {
|
|
setIsOnline(true);
|
|
onlineManager.setOnline(true);
|
|
};
|
|
const handleOffline = () => {
|
|
setIsOnline(false);
|
|
onlineManager.setOnline(false);
|
|
};
|
|
|
|
window.addEventListener("online", handleOnline);
|
|
window.addEventListener("offline", handleOffline);
|
|
|
|
// Set initial state
|
|
setIsOnline(navigator.onLine);
|
|
onlineManager.setOnline(navigator.onLine);
|
|
|
|
return () => {
|
|
window.removeEventListener("online", handleOnline);
|
|
window.removeEventListener("offline", handleOffline);
|
|
};
|
|
}
|
|
|
|
// For React Native, we could use NetInfo but it requires additional setup
|
|
// For now, assume online on mobile (React Query handles network errors gracefully)
|
|
return undefined;
|
|
}, []);
|
|
|
|
// Listen for reload messages from extension
|
|
useEffect(() => {
|
|
if (Platform.OS === "web") {
|
|
const handleMessage = (event: MessageEvent) => {
|
|
if (event.data?.type === "cal-companion-reload-cache") {
|
|
queryClient.invalidateQueries();
|
|
}
|
|
};
|
|
|
|
window.addEventListener("message", handleMessage);
|
|
return () => window.removeEventListener("message", handleMessage);
|
|
}
|
|
return undefined;
|
|
}, [queryClient]);
|
|
|
|
/**
|
|
* Invalidate all queries and trigger refetch
|
|
*/
|
|
const invalidateAllQueries = useCallback(async () => {
|
|
await queryClient.invalidateQueries();
|
|
}, [queryClient]);
|
|
|
|
/**
|
|
* Clear the persisted cache
|
|
*/
|
|
const clearCache = useCallback(async () => {
|
|
queryClient.clear();
|
|
await clearQueryCache();
|
|
}, [queryClient]);
|
|
|
|
const contextValue: QueryContextValue = useMemo(
|
|
() => ({
|
|
invalidateAllQueries,
|
|
clearCache,
|
|
isOnline,
|
|
}),
|
|
[invalidateAllQueries, clearCache, isOnline]
|
|
);
|
|
|
|
return (
|
|
<QueryContext.Provider value={contextValue}>
|
|
<PersistQueryClientProvider
|
|
client={queryClient}
|
|
persistOptions={{
|
|
persister,
|
|
maxAge: CACHE_CONFIG.persistence.maxAge,
|
|
dehydrateOptions: {
|
|
shouldDehydrateQuery: (query) => {
|
|
// Only persist successful queries
|
|
return query.state.status === "success";
|
|
},
|
|
},
|
|
}}
|
|
>
|
|
{children}
|
|
</PersistQueryClientProvider>
|
|
</QueryContext.Provider>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Hook to access query utilities
|
|
*/
|
|
export function useQueryContext(): QueryContextValue {
|
|
const context = React.useContext(QueryContext);
|
|
if (context === undefined) {
|
|
throw new Error("useQueryContext must be used within a QueryProvider");
|
|
}
|
|
return context;
|
|
}
|
|
|
|
/**
|
|
* Export for direct QueryClient access when needed
|
|
* (e.g., for prefetching outside of components)
|
|
*/
|
|
export { QueryClient };
|