Files
cal.diy/companion/contexts/QueryContext.tsx
T
Dhairyashil Shinde 2a84d46fe9 feat(companion): add Biome for linting and formatting (#26214)
* feat(companion): add ESLint with Prettier integration

- Add ESLint with eslint-config-expo/flat for SDK 55
- Integrate Prettier via eslint-plugin-prettier
- Configure environment-specific settings for Node.js config files and browser extension
- Add lint script to package.json
- Fix Prettier formatting issues in AvailabilityTab.tsx and types/index.ts

* fix(companion): use bun lockfile instead of npm

- Remove package-lock.json that was accidentally committed
- Update bun.lock with ESLint dependencies

* fix(companion): fix all ESLint errors (16 total)

- Fix unescaped entities in JSX (react/no-unescaped-entities):
  - app/(tabs)/(event-types)/index.tsx: escape quotes in delete confirmation
  - components/LoginScreen.tsx: escape apostrophe in "Don't have an account"
  - components/NetworkStatusBanner.tsx: escape apostrophes in offline message
  - components/booking-modals/BookingModals.tsx: escape apostrophe in rejection message
  - components/event-type-detail/tabs/AdvancedTab.tsx: escape quotes and apostrophes
  - components/screens/AvailabilityListScreen.tsx: escape quotes in delete confirmation

- Fix react-hooks/rules-of-hooks in RescheduleScreen.tsx:
  - Move useMemo calls before conditional early return to ensure hooks
    are called in the same order on every render

* fix(companion): fix all ESLint warnings (68 total)

- Fix import/no-named-as-default warnings by using named imports
- Fix @typescript-eslint/no-unused-vars warnings by removing unused imports/variables
- Fix react-hooks/exhaustive-deps warnings with eslint-disable comments
- Convert unused error variables in catch blocks to bare catch
- Remove unused imports (useRouter, Alert, useSafeAreaInsets, etc.)
- All ESLint checks now pass with 0 errors and 0 warnings

* feat(companion): add pre-commit lint check with --max-warnings=0

- Add companion lint check to root lint-staged.config.mjs
- Runs 'bun run lint -- --fix --max-warnings=0' for companion files
- Follows same pattern as apps/packages lint check
- Supports SKIP_WARNINGS=1 env var to bypass warning check if needed
- Commit will fail if any ESLint errors or warnings are present

* fix(companion): restore state variables and imports removed during ESLint fixes

- Restore locationAddress, locationLink, locationPhone state variables in event-type-detail.tsx (still used in fetchEventTypeData)
- Restore Alert import in BookingListScreen.tsx (still used for report booking)
- Add eslint-disable comments for the restored state variables since they're only used by setters

* test

* test

* fix eslint pre commit check

* fix: In React Native, HTML entities like ' are rendered literally as text, not decoded like in web browsers

* refactor(companion): remove ESLint and Prettier config in preparation for Biome

* feat(companion): add Biome for linting and formatting

- Install @biomejs/biome with exact version pinning (2.3.10)
- Add biome.json with formatting settings matching previous Prettier config
- Add lint, format, check, and check:ci scripts to package.json
- Update lint-staged to use Biome instead of Prettier
- Remove Prettier and prettier-plugin-tailwindcss dependencies
- Delete .prettierrc.js configuration file

* style(companion): apply Biome formatting and fix lint-staged config

- Update lint-staged to use 'biome format' instead of 'biome check'
  (pre-commit should only format, not lint)
- Apply Biome formatting to all companion files

* feat(companion): configure Biome for strict linting with pre-commit enforcement

- Apply Biome unsafe fixes (unused imports, optional chaining, etc.)
- Configure biome.json rules to disable overly strict rules for existing code:
  - noExplicitAny, noArrayIndexKey, useIterableCallbackReturn (suspicious)
  - useExhaustiveDependencies, noUnusedFunctionParameters (correctness)
  - noStaticOnlyClass (complexity)
  - noNonNullAssertion (style)
  - noStaticElementInteractions (a11y)
- Update lint-staged to use 'biome check --write --error-on-warnings'
  (pre-commit now runs both formatting AND linting, fails on warnings)

* fix(companion): enable noNonNullAssertion rule and fix all violations

- Enable noNonNullAssertion rule in biome.json (set to 'error')
- Fix 7 violations across the codebase:
  - hooks/useBookings.ts: Add runtime check before API call
  - hooks/useEventTypes.ts: Add runtime check before API call
  - hooks/useSchedules.ts: Add runtime check before API call
  - extension/entrypoints/content.ts: Add null checks for cache and getAttribute

* fix(companion): enable noStaticOnlyClass and noStaticElementInteractions rules

- Enable noStaticOnlyClass rule in biome.json
- Enable noStaticElementInteractions rule in biome.json
- Convert CalComAPIService from static class to object literal pattern
- Convert WebAuthService from static class to object literal pattern
- Add role="tooltip" to Tooltip.web.tsx for accessibility

* fix(companion): resolve TypeScript type errors in useEffect dependencies and remove unused state property

* fix(companion): enable noUnusedFunctionParameters rule and fix 9 violations

* fix(companion): enable noUnusedVariables rule and fix 4 violations

* fix(companion): enable useIterableCallbackReturn rule and fix 11 violations

* fix(companion): enable useExhaustiveDependencies rule and fix 7 violations

* fix(companion): enable noArrayIndexKey rule and fix 10 violations

* fix(companion): enable noExplicitAny rule and fix all violations

- Enable noExplicitAny rule in biome.json
- Replace any types with proper specific types across all files:
  - calcom.ts: Add proper type definitions for API responses and inputs
  - event-types.types.ts: Add BookingLimitsCount, BookingLimitsDuration, ConfirmationPolicy types
  - buildPartialUpdatePayload.ts: Fix type mismatches for booking limits functions
  - booking-actions.ts: Add proper types for booking action handlers
  - BookingDetailScreen.tsx, MarkNoShowScreen.tsx: Add types for attendee data
  - AvailabilityListItem.ios.tsx, BookingListItem.ios.tsx: Import SFSymbols7_0 type
  - extension/content.ts: Fix slot types and __cleanup invocation issues
- Remove unused imports and interfaces

* address cubics comments

* add useHookAtTopLevel rule
2025-12-27 08:19:53 -03:00

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 };