162 lines
5.0 KiB
TypeScript
162 lines
5.0 KiB
TypeScript
"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,
|
||
};
|
||
}
|