Files
community-rule/app/(app)/create/hooks/useCreateFlowNavigation.ts
2026-04-29 18:29:16 -06:00

162 lines
5.0 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { usePathname, useRouter, useSearchParams } from "next/navigation";
import { useCallback, useLayoutEffect, useMemo } from "react";
import { useCreateFlow } from "../context/CreateFlowContext";
import type { CreateFlowStep } from "../types";
import {
type CreateFlowNavigationOptions,
type CreateFlowReviewReturnTarget,
CREATE_FLOW_REVIEW_RETURN_QUERY_KEY,
buildTemplateReviewHref,
getNextStep,
getPreviousStep,
parseCreateFlowScreenFromPathname,
resolveCreateFlowBackTarget,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY,
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE,
} from "../utils/flowSteps";
/**
* Options passed to navigation handlers (e.g. for blur before navigate)
*/
const blurActiveElement = (): void => {
if (
typeof document !== "undefined" &&
document.activeElement instanceof HTMLElement
) {
document.activeElement.blur();
}
};
/**
* Hook for Create Rule Flow navigation.
*
* Resolves the active step from `/create/{screenId}` via
* {@link parseCreateFlowScreenFromPathname} (flowSteps). Footer Back uses
* {@link resolveCreateFlowBackTarget} so template **Use without changes**
* (which skips the custom-rule segment) returns to `/create/review-template/{slug}`
* from `confirm-stakeholders` instead of `conflict-management`.
*
* Template review footer Back uses {@link buildTemplateReviewHref}s
* `?fromFlow=1` marker (and persisted `templateReviewEntryFromCreateFlow`) so
* users who came from `/create/review` return there instead of `/`.
*/
export function useCreateFlowNavigation(
options?: CreateFlowNavigationOptions,
): {
currentStep: CreateFlowStep | null;
goToNextStep: () => void;
goToPreviousStep: () => void;
goToStep: (
_step: CreateFlowStep,
_navOpts?: { reviewReturn?: CreateFlowReviewReturnTarget },
) => void;
canGoNext: () => boolean;
canGoBack: () => boolean;
nextStep: CreateFlowStep | null;
previousStep: CreateFlowStep | null;
/** On `/create/review-template/…`, footer Back should go to `/create/review`. */
templateReviewFooterBackToCreateReview: boolean;
} {
const pathname = usePathname();
const searchParams = useSearchParams();
const router = useRouter();
const { state, updateState } = useCreateFlow();
const validStep = parseCreateFlowScreenFromPathname(pathname ?? null);
useLayoutEffect(() => {
if (!pathname?.includes("/create/review-template/")) return;
if (
searchParams.get(TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY) !==
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE
) {
return;
}
if (state.templateReviewEntryFromCreateFlow === true) return;
updateState({ templateReviewEntryFromCreateFlow: true });
}, [
pathname,
searchParams,
state.templateReviewEntryFromCreateFlow,
updateState,
]);
const nextStep = getNextStep(validStep, options);
const previousStep = getPreviousStep(validStep, options);
const backTarget = useMemo(
() =>
resolveCreateFlowBackTarget(
validStep,
options,
state.templateReviewBackSlug,
),
[validStep, options?.skipCommunitySave, state.templateReviewBackSlug],
);
const goToNextStep = useCallback(() => {
blurActiveElement();
if (nextStep) {
router.push(`/create/${nextStep}`);
}
}, [router, nextStep]);
const goToPreviousStep = useCallback(() => {
blurActiveElement();
if (!backTarget) return;
if (backTarget.kind === "templateReview") {
router.push(
buildTemplateReviewHref(backTarget.slug, {
fromCreateWizard: state.templateReviewEntryFromCreateFlow === true,
}),
);
return;
}
router.push(`/create/${backTarget.step}`);
}, [router, backTarget, state.templateReviewEntryFromCreateFlow]);
const templateReviewFooterBackToCreateReview = useMemo(
() =>
Boolean(state.templateReviewEntryFromCreateFlow) ||
(pathname?.includes("/create/review-template/") &&
searchParams.get(TEMPLATE_REVIEW_FROM_CREATE_FLOW_QUERY) ===
TEMPLATE_REVIEW_FROM_CREATE_FLOW_VALUE),
[state.templateReviewEntryFromCreateFlow, pathname, searchParams],
);
const goToStep = useCallback(
(
step: CreateFlowStep,
navOpts?: { reviewReturn?: CreateFlowReviewReturnTarget },
) => {
blurActiveElement();
const params = new URLSearchParams(searchParams?.toString() ?? "");
if (navOpts?.reviewReturn != null) {
params.set(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY, navOpts.reviewReturn);
} else {
params.delete(CREATE_FLOW_REVIEW_RETURN_QUERY_KEY);
}
const qs = params.toString();
router.push(qs.length > 0 ? `/create/${step}?${qs}` : `/create/${step}`);
},
[router, searchParams],
);
const canGoNext = useCallback(() => nextStep !== null, [nextStep]);
const canGoBack = useCallback(() => backTarget != null, [backTarget]);
return {
currentStep: validStep,
goToNextStep,
goToPreviousStep,
goToStep,
canGoNext,
canGoBack,
nextStep,
previousStep,
templateReviewFooterBackToCreateReview,
};
}