Skip to main content

import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem';

usePaymentFlow

A state management hook for controlling the payment flow selection in multi-step submission forms. Manages flow selection (pay-at-start vs. pay-at-end), computes active wizard steps, tracks submission status, and provides animation helpers for flow transitions.

Source file: template/hooks/use-payment-flow.ts

Overview

usePaymentFlow handles the logic for choosing between two payment flows in the item submission process:

  • Pay at Start: User pays immediately and gets instant publication.
  • Pay at End: User submits details first and pays after admin approval.

The hook integrates with localStorage for persistence across sessions (via usePaymentFlowStorage), computes which wizard steps should be active based on the selected flow, and manages a submissionStatus state machine that governs whether the user can proceed through the wizard.

Signature

function usePaymentFlow(options?: UsePaymentFlowOptions): UsePaymentFlowReturn

Parameters

ParameterTypeDefaultDescription
optionsUsePaymentFlowOptions{}Optional configuration for the hook

UsePaymentFlowOptions

PropertyTypeDefaultDescription
initialFlowPaymentFlowPaymentFlow.PAY_AT_ENDThe default flow when no stored selection exists
enableAnimationsbooleantrueWhether to enable transition animations when switching flows
autoSavebooleantrueWhether to persist the selected flow to localStorage

Return Value

State

PropertyTypeDescription
selectedFlowPaymentFlowThe currently selected payment flow
flowConfigPaymentFlowConfigFull configuration object for the selected flow (title, description, features, benefits, colors)
submissionStatusSubmissionStatusCurrent status of the submission in the flow
isAnimatingbooleanWhether a flow switch animation is in progress
isSelectingbooleanWhether a flow selection is being processed (includes animation delay)

Actions

PropertyTypeDescription
setSelectedFlow(flow: PaymentFlow) => voidDirectly set the selected flow (with optional auto-save)
selectFlow(flow: PaymentFlow) => Promise<void>Select a flow with animation delay and status reset. No-ops if already selecting.
resetFlow() => voidReset to the initial flow, clear submission status, and stop all animations

Computed Values

PropertyTypeDescription
isPayAtStartbooleanWhether the selected flow is PAY_AT_START
isPayAtEndbooleanWhether the selected flow is PAY_AT_END
shouldShowPaymentStepbooleanWhether the payment step should be visible given the current flow and submission status
activeStepsnumber[]Array of active step numbers for the wizard UI
canProceedbooleanWhether the user is allowed to move forward based on submission status and selected flow

Animation Helpers

PropertyTypeDescription
triggerAnimation() => voidManually trigger a 500ms animation cycle. No-ops if animations are disabled.
getAnimationDelay(index: number) => numberReturns index * 100 ms delay for staggered list animations. Returns 0 when animations are disabled.

Types

PaymentFlow

enum PaymentFlow {
PAY_AT_START = "pay_at_start",
PAY_AT_END = "pay_at_end",
}

SubmissionStatus

enum SubmissionStatus {
DRAFT = "draft",
PENDING_PAYMENT = "pending_payment",
PAID = "paid",
PUBLISHED = "published",
REJECTED = "rejected",
}

PaymentFlowConfig

interface PaymentFlowConfig {
id: PaymentFlow;
title: string;
subtitle: string;
description: string;
icon: string;
color: string;
bgColor: string;
borderColor: string;
darkBgColor: string;
darkBorderColor: string;
features: string[];
benefits: Array<{ icon: string; text: string; color: string }>;
badge?: string;
isDefault?: boolean;
}

Implementation Details

Step Computation

The activeSteps array is computed based on the selected flow:

  • Both flows: Steps 1 (choose flow) and 2 (details) are always active, plus step 4 (review).
  • Pay at Start: Adds step 3 (payment before review).
  • Pay at End: Adds step 5 (payment after review/approval).

canProceed State Machine

The canProceed value follows this logic:

SubmissionStatuscanProceed
DRAFTtrue
PENDING_PAYMENTtrue (either flow)
PAIDtrue only for PAY_AT_START
PUBLISHEDtrue only for PAY_AT_END
REJECTEDfalse

shouldShowPaymentStep Logic

  • Pay at Start: Shows payment when status is DRAFT or PENDING_PAYMENT.
  • Pay at End: Shows payment when status is PAID (meaning review is complete, payment pending).

SSR Hydration Safety

The hook initializes with initialFlow on the server and synchronizes with the localStorage value after client-side hydration via a useEffect. The isHydrated flag prevents localStorage writes during server rendering.

Flow Selection with Animation

The selectFlow function is debounced -- it returns early if isSelecting is already true. When animations are enabled, it introduces a 150ms delay for a smoother UX before committing the selection.

Usage Examples

Flow selection UI

import { usePaymentFlow } from '@/hooks/use-payment-flow';
import { PaymentFlow } from '@/lib/payment/types/payment';

function FlowSelector() {
const {
selectedFlow,
selectFlow,
flowConfig,
isSelecting,
getAnimationDelay,
} = usePaymentFlow();

return (
<div className="grid grid-cols-2 gap-4">
{(['pay_at_start', 'pay_at_end'] as PaymentFlow[]).map((flow, i) => (
<button
key={flow}
onClick={() => selectFlow(flow)}
disabled={isSelecting}
className={selectedFlow === flow ? 'ring-2 ring-primary' : ''}
style={{ animationDelay: `${getAnimationDelay(i)}ms` }}
>
{flow === 'pay_at_start' ? 'Pay First' : 'Pay Later'}
</button>
))}

<div className="col-span-2 mt-4">
<h3>{flowConfig.title}</h3>
<p>{flowConfig.description}</p>
<ul>
{flowConfig.features.map((f) => (
<li key={f}>{f}</li>
))}
</ul>
</div>
</div>
);
}

Multi-step wizard integration

import { usePaymentFlow } from '@/hooks/use-payment-flow';

function SubmissionWizard() {
const {
selectedFlow,
activeSteps,
canProceed,
shouldShowPaymentStep,
isPayAtStart,
submissionStatus,
} = usePaymentFlow();

return (
<div>
<StepIndicator steps={activeSteps} />

<Step1FlowSelection />
<Step2DetailsForm />

{isPayAtStart && shouldShowPaymentStep && (
<Step3Payment />
)}

<Step4Review />

{!isPayAtStart && shouldShowPaymentStep && (
<Step5Payment />
)}

<button disabled={!canProceed}>
{submissionStatus === 'draft' ? 'Continue' : 'Submit'}
</button>
</div>
);
}

Disabling animations and auto-save

function SimplifiedFlowPicker() {
const { selectedFlow, setSelectedFlow, flowConfig } = usePaymentFlow({
enableAnimations: false,
autoSave: false,
initialFlow: PaymentFlow.PAY_AT_START,
});

return (
<select
value={selectedFlow}
onChange={(e) => setSelectedFlow(e.target.value as PaymentFlow)}
>
<option value="pay_at_start">Pay First</option>
<option value="pay_at_end">Pay Later</option>
</select>
);
}

Resetting the flow

function ResetableFlow() {
const { resetFlow, selectedFlow, submissionStatus } = usePaymentFlow();

return (
<div>
<p>Flow: {selectedFlow} | Status: {submissionStatus}</p>
<button onClick={resetFlow}>Start Over</button>
</div>
);
}

Requirements

DependencyPurpose
reactuseState, useCallback, useMemo, useEffect
@/lib/payment/types/paymentPaymentFlow and SubmissionStatus enums
@/lib/config/payment-flowsgetPaymentFlowConfig for flow configuration lookup
./use-local-storageusePaymentFlowStorage for persisting the selection