Improve onboarding flow
This commit is contained in:
@@ -2,8 +2,9 @@
|
||||
* Onboarding React Query hooks
|
||||
*/
|
||||
import { useMutation, useQuery, useQueryClient, UseQueryOptions, UseMutationOptions } from '@tanstack/react-query';
|
||||
import { useCallback, useRef } from 'react';
|
||||
import { onboardingService } from '../services/onboarding';
|
||||
import { UserProgress, UpdateStepRequest } from '../types/onboarding';
|
||||
import { UserProgress, UpdateStepRequest, StepDraftResponse } from '../types/onboarding';
|
||||
import { ApiError } from '../client';
|
||||
|
||||
// Query Keys
|
||||
@@ -12,6 +13,7 @@ export const onboardingKeys = {
|
||||
progress: (userId: string) => [...onboardingKeys.all, 'progress', userId] as const,
|
||||
steps: () => [...onboardingKeys.all, 'steps'] as const,
|
||||
stepDetail: (stepName: string) => [...onboardingKeys.steps(), stepName] as const,
|
||||
stepDraft: (stepName: string) => [...onboardingKeys.all, 'draft', stepName] as const,
|
||||
} as const;
|
||||
|
||||
// Queries
|
||||
@@ -122,8 +124,6 @@ export const useMarkStepCompleted = (
|
||||
// Invalidate queries on error to ensure we get fresh data
|
||||
queryClient.invalidateQueries({ queryKey: onboardingKeys.progress(userId) });
|
||||
},
|
||||
// Prevent duplicate requests by using the step name as a mutation key
|
||||
mutationKey: (variables) => ['markStepCompleted', variables?.userId, variables?.stepName],
|
||||
...options,
|
||||
});
|
||||
};
|
||||
@@ -132,7 +132,7 @@ export const useResetProgress = (
|
||||
options?: UseMutationOptions<UserProgress, ApiError, string>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
|
||||
return useMutation<UserProgress, ApiError, string>({
|
||||
mutationFn: (userId: string) => onboardingService.resetProgress(userId),
|
||||
onSuccess: (data, userId) => {
|
||||
@@ -141,4 +141,110 @@ export const useResetProgress = (
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
// Draft Queries and Mutations
|
||||
|
||||
/**
|
||||
* Query hook to get draft data for a specific step
|
||||
*/
|
||||
export const useStepDraft = (
|
||||
stepName: string,
|
||||
options?: Omit<UseQueryOptions<StepDraftResponse, ApiError>, 'queryKey' | 'queryFn'>
|
||||
) => {
|
||||
return useQuery<StepDraftResponse, ApiError>({
|
||||
queryKey: onboardingKeys.stepDraft(stepName),
|
||||
queryFn: () => onboardingService.getStepDraft(stepName),
|
||||
enabled: !!stepName,
|
||||
staleTime: 30 * 1000, // 30 seconds
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to save draft data for a step
|
||||
*/
|
||||
export const useSaveStepDraft = (
|
||||
options?: UseMutationOptions<{ success: boolean }, ApiError, { stepName: string; draftData: Record<string, any> }>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, { stepName: string; draftData: Record<string, any> }>({
|
||||
mutationFn: ({ stepName, draftData }) => onboardingService.saveStepDraft(stepName, draftData),
|
||||
onSuccess: (_, { stepName }) => {
|
||||
// Invalidate the draft query to get fresh data
|
||||
queryClient.invalidateQueries({ queryKey: onboardingKeys.stepDraft(stepName) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Mutation hook to delete draft data for a step
|
||||
*/
|
||||
export const useDeleteStepDraft = (
|
||||
options?: UseMutationOptions<{ success: boolean }, ApiError, string>
|
||||
) => {
|
||||
const queryClient = useQueryClient();
|
||||
|
||||
return useMutation<{ success: boolean }, ApiError, string>({
|
||||
mutationFn: (stepName: string) => onboardingService.deleteStepDraft(stepName),
|
||||
onSuccess: (_, stepName) => {
|
||||
// Invalidate the draft query
|
||||
queryClient.invalidateQueries({ queryKey: onboardingKeys.stepDraft(stepName) });
|
||||
},
|
||||
...options,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Custom hook with debounced draft auto-save functionality.
|
||||
* Automatically saves draft data after a delay when form data changes.
|
||||
*/
|
||||
export const useAutoSaveDraft = (stepName: string, debounceMs: number = 2000) => {
|
||||
const saveStepDraft = useSaveStepDraft();
|
||||
const timeoutRef = useRef<NodeJS.Timeout | null>(null);
|
||||
|
||||
const saveDraft = useCallback(
|
||||
(draftData: Record<string, any>) => {
|
||||
// Clear any existing timeout
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
|
||||
// Set a new timeout for debounced save
|
||||
timeoutRef.current = setTimeout(() => {
|
||||
saveStepDraft.mutate({ stepName, draftData });
|
||||
}, debounceMs);
|
||||
},
|
||||
[stepName, debounceMs, saveStepDraft]
|
||||
);
|
||||
|
||||
const saveDraftImmediately = useCallback(
|
||||
(draftData: Record<string, any>) => {
|
||||
// Clear any pending debounced save
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
}
|
||||
// Save immediately
|
||||
saveStepDraft.mutate({ stepName, draftData });
|
||||
},
|
||||
[stepName, saveStepDraft]
|
||||
);
|
||||
|
||||
const cancelPendingSave = useCallback(() => {
|
||||
if (timeoutRef.current) {
|
||||
clearTimeout(timeoutRef.current);
|
||||
timeoutRef.current = null;
|
||||
}
|
||||
}, []);
|
||||
|
||||
return {
|
||||
saveDraft,
|
||||
saveDraftImmediately,
|
||||
cancelPendingSave,
|
||||
isSaving: saveStepDraft.isPending,
|
||||
isError: saveStepDraft.isError,
|
||||
error: saveStepDraft.error,
|
||||
};
|
||||
};
|
||||
Reference in New Issue
Block a user