* refactor: migrate UnconfirmedBookingBadge from features to apps/web Move UnconfirmedBookingBadge.tsx from packages/features/bookings/ to apps/web/modules/bookings/components/ as part of the architectural refactor to remove trpc client imports from the features layer. Also removes unused preserveBookingsQueryParams function from Navigation.tsx. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * refactor: move shell navigation and badges to apps/web Move shell navigation components and trpc-using badges from packages/features to apps/web/modules to fix circular dependency: - Move navigation folder to apps/web/modules/shell/navigation/ - Move TeamInviteBadge.tsx to apps/web/modules/shell/ - Create Shell wrapper in apps/web that provides MobileNavigationContainer - Update all Shell imports in apps/web to use the new wrapper - Remove MobileNavigationContainer default from features Shell.tsx - Fix pre-existing lint warnings in touched files This establishes the pattern for migrating React components that use trpc hooks from the features layer to the web app layer, ensuring proper dependency direction: apps/web imports from packages/features, never the reverse. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: move SideBar.tsx to apps/web to fix build error SideBar.tsx was importing Navigation from the moved navigation folder, causing a build error. Moving SideBar.tsx to apps/web and updating the features Shell to not have a default SidebarContainer fixes this. The web Shell wrapper now provides both the default SidebarContainer and MobileNavigationContainer, maintaining the injection pattern. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * revert * revert * revert * wip * wip * wip * wip * wip * wip * not used anywhere * wip * wip * wip * wip * fix * fix * wip * wip * wip * wip * wip * fix * fix * fix * fix * migrate * migrate admin-adpi * wip * feat: migrate organization settings components from packages/features to apps/web/modules - Migrate profile.tsx, appearance.tsx, general.tsx, privacy.tsx, guest-notifications.tsx, delegationCredential.tsx, other-team-members-view.tsx, other-team-profile-view.tsx - Migrate attributes directory (AttributesForm.tsx, DeleteAttributeModal.tsx, ListSkeleton.tsx, attributes-create-view.tsx, attributes-edit-view.tsx, attributes-list-view.tsx) - Migrate admin directory (AdminOrgEditPage.tsx, AdminOrgPage.tsx, WorkspacePlatformPage.tsx) - Update all page imports to use new paths from ~/settings/organizations/ - Update relative imports in migrated files to use @calcom/features paths - Fix lint warnings in migrated files Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update test import path after migration Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: remove unnecessary test-setup import (already in vitest config) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * wip * refactor: delete original files after migration to apps/web/modules Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * fix * fix * fix * wip * refactor more * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * mv * update import paths * wip * wip * fix * fix * fix * fix * fix * fix * fix * mv * mv * mv * fix * wip * wip * fix * fix * fix * fix: make test mocks resilient to vi.resetAllMocks() Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: fix AttributeForm test failures Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix * fix * refactor: move ee files to apps/web/modules/ee/ folder - Move teams, workflows, and organizations folders to apps/web/modules/ee/ - Add LICENSE file to apps/web/modules/ee/ - Update all import paths from ~/teams/ to ~/ee/teams/ - Update all import paths from ~/settings/organizations/ to ~/ee/organizations/ - Remove duplicate MemberInvitationModal copy.tsx file Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: move useHasPaidPlan and dependent files to apps/web/modules - Move useHasPaidPlan.ts from packages/features/billing/hooks to apps/web/modules/billing/hooks - Move intercom files from packages/features/ee/support to apps/web/modules/ee/support - Move ContactMenuItem.tsx and dependencies (freshchat, helpscout, zendesk) to apps/web/modules/ee/support - Move ViewRecordingsDialog.tsx and RecordingListSkeleton to apps/web/modules/ee/video - Move CalVideoSettings.tsx to apps/web/modules/eventtypes/components/locations - Move CreateOrEditOutOfOfficeModal.tsx to apps/web/modules/settings/outOfOffice - Refactor UpgradeTeamsBadge to accept props and create wrapper in apps/web/modules/billing - Update all callers to use new file locations - Add eslint-disable comments for pre-existing lint warnings This fixes the tRPC server build failure caused by circular dependency where the server build was traversing into packages/features and pulling in React hooks that depend on @calcom/trpc/react types. Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: correct UpgradeTeamsBadge import path to use package export Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * wip * fix * fix * fix * fix * fix * fix * fix * fix: pass plan state through SelectProps to UpgradeTeamsBadge - Add upgradeTeamsBadgeProps field to ExtendedOption type in Select component - Update OptionComponent to spread upgradeTeamsBadgeProps to UpgradeTeamsBadge - Update getOptions.ts to accept PlanState object and include upgradeTeamsBadgeProps - Update WorkflowStepContainer.tsx to pass planState to getWorkflowTriggerOptions/getWorkflowTemplateOptions - Update WorkflowDetailsPage.tsx to pass upgradeTeamsBadgeProps in transformed action options - Update AddActionDialog.tsx interface and mapping to include upgradeTeamsBadgeProps - Add eslint-disable comments for pre-existing React Hook dependency warnings This fixes the UpgradeTeamsBadge refactoring issue where the badge was always showing 'upgrade' text instead of the correct text based on plan state (trial_mode, inactive_team_plan, etc.) Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update import paths to use /ee/ folder for workflows and organizations Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: update workflow component imports to use /ee/ folder Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: add missing types.ts for LocationInput.tsx Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix: extract BookingRedirectForm type to shared location Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * fix * wip * wip * fix: update BookingRedirectForm import to use local types file Co-Authored-By: benny@cal.com <sldisek783@gmail.com> * wip * fix * fix --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
294 lines
8.5 KiB
TypeScript
294 lines
8.5 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 { CACHE_CONFIG, queryKeys } from "../config/cache.config";
|
|
import { CalComAPIService, EventType, CreateEventTypeInput } from "../services/calcom";
|
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
|
|
|
/**
|
|
* 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: () => 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 };
|