Files
calendar/packages/ui/components/form/wizard/WizardForm.tsx
T
sean-brydonGitHubDevin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
19563aa697 feat: Self hosted onboarding (#22102)
* intro work

* update wixard form to have content callback to remove preset navigation

* more fixes to deployment

* fix calling static service

* fix save license key text

* ensure default steps work as expected

* fix conditional for rendering step

* skip step

* add on next step for free license

* refactor wizard form to use nuqs

* fix styles

* merge base param with step config

* fix next stepo text

* use deployment Signature token

* decrypt signature token

* fix: resolve type errors and test failures from wizard form refactor

- Fix signatureToken field name to signatureTokenEncrypted in deployment repository
- Add missing getSignatureToken method to verifyApiKey test mock
- Fix WizardForm import from default to named export in test file
- Add missing nextStep prop to Steps component in WizardForm

Resolves TypeScript type check errors and unit test failures without changing functionality.

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* fix: add missing getDeploymentSignatureToken mock in LicenseKeyService test

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* fix: add nuqs library mock for WizardForm test

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* fix: add missing nav prop to AdminAppsList component with eslint disable

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* Apply suggestions from code review

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* Update apps/web/modules/auth/setup-view.tsx

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix license schema changes

* revret schema generation

* fix eslint errors

* remove required nav type + add use client

* fix types

* Update packages/ui/components/form/wizard/useWizardState.ts

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>

* fix controller issue

* add checks for deployment key being null - add more tests

* fix tests

* add deployment key tests

* fix: resolve crypto mock to handle empty encryption keys gracefully

- Updated symmetricDecrypt mock to return null instead of throwing 'Invalid key' error when encryption key is empty
- All getDeploymentKey tests now pass including the previously failing 'should return null when decryption fails due to missing encryption key' test
- Fixes mocking issues in PR 22102 self-hosted onboarding wizard form refactor

Co-Authored-By: sean@cal.com <Sean@brydon.io>

* fix label

* add i18n to error

* use enum for steps

* add as const

* fix test env issues

---------

Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
2025-07-09 09:26:01 +01:00

121 lines
3.6 KiB
TypeScript

"use client";
import type { Dispatch, SetStateAction } from "react";
import { useEffect, useState } from "react";
import classNames from "@calcom/ui/classNames";
import { Button } from "../../button";
import { Steps } from "../../form/step";
import { useWizardState } from "./useWizardState";
export type WizardStep = {
title: string;
containerClassname?: string;
contentClassname?: string;
description: string;
content?:
| ((
setIsPending: Dispatch<SetStateAction<boolean>>,
nav: { onNext: () => void; onPrev: () => void; step: number; maxSteps: number }
) => JSX.Element)
| JSX.Element;
isEnabled?: boolean;
isPending?: boolean;
customActions?: boolean;
};
export interface WizardFormProps {
steps: WizardStep[];
containerClassname?: string;
prevLabel?: string;
nextLabel?: string;
finishLabel?: string;
stepLabel?: React.ComponentProps<typeof Steps>["stepLabel"];
defaultStep?: number;
disableNavigation?: boolean;
}
export function WizardForm({
steps,
containerClassname,
prevLabel = "Back",
nextLabel = "Next",
finishLabel = "Finish",
stepLabel,
defaultStep = 1,
disableNavigation = false,
}: WizardFormProps) {
const { currentStep, maxSteps, nextStep, prevStep, isFirstStep, isLastStep } = useWizardState(
defaultStep,
steps.length
);
const [currentStepisPending, setCurrentStepisPending] = useState(false);
const currentStepData = steps[currentStep - 1];
useEffect(() => {
setCurrentStepisPending(false);
}, [currentStep]);
return (
<div className="mx-auto mt-4 print:w-full" data-testid="wizard-form">
<div className={classNames("overflow-hidden md:mb-2 md:w-[700px]", containerClassname)}>
<div className="px-6 py-5">
<h1 className="font-cal text-emphasis text-2xl" data-testid="step-title">
{currentStepData.title}
</h1>
<p className="text-subtle text-sm" data-testid="step-description">
{currentStepData.description}
</p>
{!disableNavigation && (
<Steps
maxSteps={maxSteps}
currentStep={currentStep}
nextStep={nextStep}
stepLabel={stepLabel}
data-testid="wizard-step-component"
disableNavigation={disableNavigation}
/>
)}
</div>
</div>
<div className={classNames("mb-8 overflow-hidden md:w-[700px]", containerClassname)}>
<div
className={classNames(
"bg-default border-subtle max-w-3xl rounded-2xl border px-4 py-3 sm:p-4 ",
currentStepData.contentClassname
)}>
{typeof currentStepData.content === "function"
? currentStepData.content(setCurrentStepisPending, {
onNext: nextStep,
onPrev: prevStep,
step: currentStep,
maxSteps,
})
: currentStepData.content}
</div>
{!disableNavigation && !currentStepData.customActions && (
<div className="flex justify-end px-4 py-4 print:hidden sm:px-6">
{!isFirstStep && (
<Button color="secondary" onClick={prevStep}>
{prevLabel}
</Button>
)}
<Button
tabIndex={0}
loading={currentStepisPending}
type="submit"
color="primary"
form={`wizard-step-${currentStep}`}
disabled={currentStepData.isEnabled === false}
className="relative ml-2">
{isLastStep ? finishLabel : nextLabel}
</Button>
</div>
)}
</div>
</div>
);
}