"use client"; import { Suspense, useCallback, useEffect, useState, type ReactNode, } from "react"; import { usePathname, useRouter } from "next/navigation"; import { CreateFlowProvider, useCreateFlow } from "./context/CreateFlowContext"; import { useCreateFlowNavigation } from "./hooks/useCreateFlowNavigation"; import { useCreateFlowExit } from "./hooks/useCreateFlowExit"; import CreateFlowTopNav from "../components/utility/CreateFlowTopNav"; import { getStepIndex } from "./utils/flowSteps"; import { createFlowStepUsesCenteredTextLayout } from "./utils/createFlowScreenRegistry"; import CreateFlowFooter from "../components/utility/CreateFlowFooter"; import Button from "../components/buttons/Button"; import { buildPublishPayload } from "../../lib/create/buildPublishPayload"; import { fetchAuthSession, publishRule } from "../../lib/create/api"; import { writeLastPublishedRule } from "../../lib/create/lastPublishedRule"; import { fetchTemplateBySlug, type RuleTemplateDto, } from "../../lib/create/fetchTemplates"; import messages from "../../messages/en/index"; import { useAuthModal } from "../contexts/AuthModalContext"; import { useTranslation } from "../contexts/MessagesContext"; import { PostLoginDraftTransfer } from "./PostLoginDraftTransfer"; import { SignedInDraftHydration } from "./SignedInDraftHydration"; import Alert from "../components/modals/Alert"; import { CreateFlowDraftSaveBannerProvider, useCreateFlowDraftSaveBanner, } from "./context/CreateFlowDraftSaveBannerContext"; /** First step where Save & Exit is offered (first Create Community select per Figma). */ const SAVE_EXIT_FROM_STEP_INDEX = getStepIndex("community-size"); function CreateFlowSessionShell({ children }: { children: ReactNode }) { const [sessionUser, setSessionUser] = useState< { id: string; email: string } | null | undefined >(undefined); useEffect(() => { let cancelled = false; void fetchAuthSession().then(({ user }) => { if (!cancelled) setSessionUser(user); }); return () => { cancelled = true; }; }, []); const sessionResolved = sessionUser !== undefined; const enableAnonymousPersistence = sessionResolved && sessionUser === null; return ( {children} ); } function CreateFlowLayoutContent({ children, sessionUser, sessionResolved, }: { children: ReactNode; sessionUser: { id: string; email: string } | null | undefined; sessionResolved: boolean; }) { const tFooter = useTranslation("create.footer"); const router = useRouter(); const pathname = usePathname(); const { openLogin } = useAuthModal(); const { currentStep, nextStep, previousStep, goToNextStep, goToPreviousStep, } = useCreateFlowNavigation(); const { state, clearState } = useCreateFlow(); const { draftSaveBannerMessage, setDraftSaveBannerMessage } = useCreateFlowDraftSaveBanner(); const [publishBannerMessage, setPublishBannerMessage] = useState< string | null >(null); const [isPublishing, setIsPublishing] = useState(false); const [templateReviewApplyError, setTemplateReviewApplyError] = useState< string | null >(null); const [isApplyingTemplate, setIsApplyingTemplate] = useState(false); const templateReviewMatch = pathname?.match( /\/create\/review-template\/([^/?#]+)/, ); const templateReviewSlug = templateReviewMatch?.[1] ? decodeURIComponent(templateReviewMatch[1]) : null; /** Match anywhere in path so locale/basePath variants still get template footer + layout. */ const isTemplateReviewRoute = Boolean( pathname?.includes("/create/review-template/"), ); const handleFinalize = useCallback(async () => { setPublishBannerMessage(null); const payloadResult = buildPublishPayload(state); if (payloadResult.ok === false) { setPublishBannerMessage( payloadResult.error === "missingCommunityName" ? messages.create.publish.missingCommunityName : payloadResult.error, ); return; } const { title, summary, document: ruleDocument } = payloadResult; setIsPublishing(true); const publishResult = await publishRule({ title, summary, document: ruleDocument, }); setIsPublishing(false); if (publishResult.ok === true) { writeLastPublishedRule({ id: publishResult.id, title, summary: summary ?? null, document: ruleDocument, }); router.push("/create/completed"); return; } if (publishResult.status === 401) { openLogin({ variant: "default", nextPath: "/create/final-review?syncDraft=1", backdropVariant: "blurredYellow", }); return; } setPublishBannerMessage( publishResult.error.trim() !== "" ? publishResult.error : messages.create.publish.genericPublishFailed, ); }, [state, router, openLogin]); const handleUseTemplateWithoutChanges = useCallback(async () => { if (!templateReviewSlug) return; setTemplateReviewApplyError(null); setIsApplyingTemplate(true); const result = await fetchTemplateBySlug(templateReviewSlug); setIsApplyingTemplate(false); if (result === null) { setTemplateReviewApplyError(messages.create.templateReview.errors.notFound); return; } if ("error" in result) { setTemplateReviewApplyError(result.error); return; } const template: RuleTemplateDto = result; const doc = template.body; if (!doc || typeof doc !== "object" || Array.isArray(doc)) { setTemplateReviewApplyError(messages.create.templateReview.errors.applyFailed); return; } const summaryRaw = typeof template.description === "string" ? template.description.trim() : ""; writeLastPublishedRule({ id: `template:${template.slug}`, title: template.title, summary: summaryRaw.length > 0 ? summaryRaw : null, document: doc as Record, }); router.push("/create/completed"); }, [router, templateReviewSlug]); const runAuthenticatedExit = useCreateFlowExit({ state, currentStep, clearState, router, user: sessionUser ?? null, setDraftSaveBannerMessage, }); const handleExit = async (opts?: { saveDraft?: boolean }) => { const saveDraft = opts?.saveDraft ?? false; if (!sessionResolved) return; if (sessionUser === null) { if (saveDraft) return; const returnToTemplateReview = templateReviewSlug != null ? `/create/review-template/${encodeURIComponent(templateReviewSlug)}?syncDraft=1` : null; openLogin({ variant: "saveProgress", nextPath: returnToTemplateReview ?? `${pathname ?? "/create"}?syncDraft=1`, backdropVariant: "blurredYellow", }); return; } if (!sessionUser) return; await runAuthenticatedExit(opts); }; const isCompletedStep = currentStep === "completed"; const isRightRailStep = currentStep === "right-rail"; const isFinalReviewStep = currentStep === "final-review"; const isCardsStep = currentStep === "cards"; const stepIdx = currentStep != null ? getStepIndex(currentStep) : -1; /** At `md+`, main cross-axis: center by default; exceptions stay top-aligned (see product spec). */ const mainContentClass = isCompletedStep ? "items-stretch overflow-y-auto md:overflow-hidden" : isRightRailStep ? "items-stretch overflow-hidden" : isFinalReviewStep || isCardsStep || isTemplateReviewRoute ? "items-start justify-center overflow-y-auto" : "items-start justify-center overflow-y-auto md:items-center"; const isTextStep = createFlowStepUsesCenteredTextLayout(currentStep); const mainMaxMdJustify = isTextStep && !isCompletedStep && !isRightRailStep ? "max-md:justify-center" : "max-md:justify-start"; const mainMaxMdCross = isCompletedStep || isRightRailStep ? "max-md:flex-col max-md:items-stretch" : "max-md:flex-col max-md:items-center"; const mainResponsiveLayout = `${mainMaxMdCross} ${mainMaxMdJustify} md:flex-row md:justify-center`; const saveDraftOnExit = Boolean(sessionUser) && stepIdx >= SAVE_EXIT_FROM_STEP_INDEX; const hasErrorOverlays = Boolean(draftSaveBannerMessage) || Boolean(publishBannerMessage) || Boolean(templateReviewApplyError); return (
{hasErrorOverlays ? (
{draftSaveBannerMessage ? (
setDraftSaveBannerMessage(null)} className="w-full" />
) : null} {publishBannerMessage ? (
setPublishBannerMessage(null)} className="w-full" />
) : null} {templateReviewApplyError ? (
setTemplateReviewApplyError(null)} className="w-full" />
) : null}
) : null} router.push("/create/final-review") : undefined } onExit={(opts) => void handleExit(opts)} buttonPalette={isCompletedStep ? "inverse" : undefined} className={`shrink-0 ${ isCompletedStep ? "!bg-[var(--color-teal-teal50,#c9fef9)]" : "" }`.trim()} />
{children}
{!isCompletedStep && (
) : nextStep ? ( ) : null } onBackClick={ isTemplateReviewRoute ? () => router.push("/") : previousStep ? goToPreviousStep : undefined } /> )} ); } export default function CreateFlowLayoutClient({ children, }: { children: ReactNode; }) { return {children}; }