* 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
298 lines
8.6 KiB
TypeScript
298 lines
8.6 KiB
TypeScript
/**
|
|
* Event Types Query Hooks
|
|
*
|
|
* This module provides React Query hooks for fetching and mutating event types.
|
|
* It integrates with the existing CalComAPIService and provides:
|
|
* - Automatic caching with configurable stale times
|
|
* - Pull-to-refresh support via refetch
|
|
* - Optimistic updates for mutations
|
|
* - Cache invalidation on create/update/delete
|
|
*/
|
|
|
|
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
import { CACHE_CONFIG, queryKeys } from "../config/cache.config";
|
|
import { CalComAPIService, type CreateEventTypeInput, type EventType } from "../services/calcom";
|
|
|
|
/**
|
|
* Hook to fetch all event types
|
|
*
|
|
* @returns Query result with event types data, loading state, error, and refetch function
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { data: eventTypes, isLoading, refetch, isRefetching } = useEventTypes();
|
|
*
|
|
* // Pull-to-refresh
|
|
* <RefreshControl refreshing={isRefetching} onRefresh={refetch} />
|
|
* ```
|
|
*/
|
|
export function useEventTypes() {
|
|
return useQuery({
|
|
queryKey: queryKeys.eventTypes.lists(),
|
|
queryFn: () => CalComAPIService.getEventTypes(),
|
|
staleTime: CACHE_CONFIG.eventTypes.staleTime,
|
|
// Keep previous data while fetching new data (smoother UX)
|
|
placeholderData: (previousData) => previousData,
|
|
// Don't retry on network errors (keeps cache intact)
|
|
retry: (failureCount, error) => {
|
|
if (error?.message?.includes("Network") || error?.message?.includes("fetch")) {
|
|
return false;
|
|
}
|
|
return failureCount < 2;
|
|
},
|
|
refetchOnReconnect: true,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to fetch a single event type by ID
|
|
*
|
|
* @param id - The ID of the event type
|
|
* @returns Query result with event type data
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { data: eventType, isLoading } = useEventTypeById(123);
|
|
* ```
|
|
*/
|
|
export function useEventTypeById(id: number | undefined) {
|
|
return useQuery({
|
|
queryKey: queryKeys.eventTypes.detail(id || 0),
|
|
queryFn: () => {
|
|
if (!id) throw new Error("id is required");
|
|
return CalComAPIService.getEventTypeById(id);
|
|
},
|
|
enabled: !!id, // Only fetch when id is provided
|
|
staleTime: CACHE_CONFIG.eventTypes.staleTime,
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to create a new event type
|
|
*
|
|
* @returns Mutation function and state
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { mutate: createEventType, isPending } = useCreateEventType();
|
|
*
|
|
* createEventType({
|
|
* title: 'Quick Chat',
|
|
* slug: 'quick-chat',
|
|
* lengthInMinutes: 15,
|
|
* });
|
|
* ```
|
|
*/
|
|
export function useCreateEventType() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (input: CreateEventTypeInput) => CalComAPIService.createEventType(input),
|
|
onSuccess: (newEventType) => {
|
|
// Invalidate the list to include the new event type
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.eventTypes.lists() });
|
|
|
|
// Optionally, add the new event type to cache immediately
|
|
queryClient.setQueryData(queryKeys.eventTypes.detail(newEventType.id), newEventType);
|
|
},
|
|
onError: (error) => {
|
|
console.error("Failed to create event type");
|
|
if (__DEV__) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
console.debug("[useCreateEventType] failed", { message, stack });
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to update an event type
|
|
*
|
|
* @returns Mutation function and state
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { mutate: updateEventType, isPending } = useUpdateEventType();
|
|
*
|
|
* updateEventType({
|
|
* id: 123,
|
|
* updates: { title: 'Updated Title' }
|
|
* });
|
|
* ```
|
|
*/
|
|
export function useUpdateEventType() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: ({ id, updates }: { id: number; updates: Partial<CreateEventTypeInput> }) =>
|
|
CalComAPIService.updateEventType(id, updates),
|
|
onSuccess: (updatedEventType, variables) => {
|
|
// Invalidate the list
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.eventTypes.lists() });
|
|
|
|
// Update the specific event type in cache
|
|
queryClient.setQueryData(queryKeys.eventTypes.detail(variables.id), updatedEventType);
|
|
},
|
|
onError: (error) => {
|
|
console.error("Failed to update event type");
|
|
if (__DEV__) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
console.debug("[useUpdateEventType] failed", { message, stack });
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to delete an event type
|
|
*
|
|
* @returns Mutation function and state
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { mutate: deleteEventType, isPending } = useDeleteEventType();
|
|
*
|
|
* deleteEventType(123);
|
|
* ```
|
|
*/
|
|
export function useDeleteEventType() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: (id: number) => CalComAPIService.deleteEventType(id),
|
|
onMutate: async (deletedId) => {
|
|
// Cancel any outgoing refetches
|
|
await queryClient.cancelQueries({ queryKey: queryKeys.eventTypes.lists() });
|
|
|
|
// Snapshot the previous value
|
|
const previousEventTypes = queryClient.getQueryData<EventType[]>(
|
|
queryKeys.eventTypes.lists()
|
|
);
|
|
|
|
// Optimistically remove from the list
|
|
if (previousEventTypes) {
|
|
queryClient.setQueryData(
|
|
queryKeys.eventTypes.lists(),
|
|
previousEventTypes.filter((et) => et.id !== deletedId)
|
|
);
|
|
}
|
|
|
|
return { previousEventTypes };
|
|
},
|
|
onError: (error, _deletedId, context) => {
|
|
// Rollback on error
|
|
if (context?.previousEventTypes) {
|
|
queryClient.setQueryData(queryKeys.eventTypes.lists(), context.previousEventTypes);
|
|
}
|
|
console.error("Failed to delete event type");
|
|
if (__DEV__) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
console.debug("[useDeleteEventType] failed", { message, stack });
|
|
}
|
|
},
|
|
onSettled: () => {
|
|
// Always refetch after error or success
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.eventTypes.lists() });
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to duplicate an event type
|
|
*
|
|
* @returns Mutation function and state
|
|
*
|
|
* @example
|
|
* ```tsx
|
|
* const { mutate: duplicateEventType, isPending } = useDuplicateEventType();
|
|
*
|
|
* duplicateEventType({
|
|
* eventType: existingEventType,
|
|
* existingEventTypes: allEventTypes
|
|
* });
|
|
* ```
|
|
*/
|
|
export function useDuplicateEventType() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return useMutation({
|
|
mutationFn: async ({
|
|
eventType,
|
|
existingEventTypes,
|
|
}: {
|
|
eventType: EventType;
|
|
existingEventTypes: EventType[];
|
|
}) => {
|
|
// Generate a new title and slug for the duplicate
|
|
const newTitle = `${eventType.title} (copy)`;
|
|
let newSlug = `${eventType.slug}-copy`;
|
|
|
|
// Check if slug already exists and append a number if needed
|
|
let counter = 1;
|
|
while (existingEventTypes.some((et) => et.slug === newSlug)) {
|
|
newSlug = `${eventType.slug}-copy-${counter}`;
|
|
counter++;
|
|
}
|
|
|
|
const duration = eventType.lengthInMinutes ?? eventType.length ?? 15;
|
|
|
|
return CalComAPIService.createEventType({
|
|
title: newTitle,
|
|
slug: newSlug,
|
|
lengthInMinutes: duration,
|
|
description: eventType.description || undefined,
|
|
});
|
|
},
|
|
onSuccess: () => {
|
|
// Invalidate the list to include the duplicated event type
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.eventTypes.lists() });
|
|
},
|
|
onError: (error) => {
|
|
console.error("Failed to duplicate event type");
|
|
if (__DEV__) {
|
|
const message = error instanceof Error ? error.message : String(error);
|
|
const stack = error instanceof Error ? error.stack : undefined;
|
|
console.debug("[useDuplicateEventType] failed", { message, stack });
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Hook to prefetch event types (useful for navigation)
|
|
*
|
|
* @returns Function to prefetch event types
|
|
*/
|
|
export function usePrefetchEventTypes() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return () => {
|
|
queryClient.prefetchQuery({
|
|
queryKey: queryKeys.eventTypes.lists(),
|
|
queryFn: () => CalComAPIService.getEventTypes(),
|
|
staleTime: CACHE_CONFIG.eventTypes.staleTime,
|
|
});
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Hook to invalidate all event types cache
|
|
*
|
|
* @returns Function to invalidate event types cache
|
|
*/
|
|
export function useInvalidateEventTypes() {
|
|
const queryClient = useQueryClient();
|
|
|
|
return () => {
|
|
queryClient.invalidateQueries({ queryKey: queryKeys.eventTypes.all });
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Type exports for consumers
|
|
*/
|
|
export type { EventType, CreateEventTypeInput };
|