feat: enhance onboarding steps with loading state and button component (#20350)

- Added loading state to ConnectedCalendars and ConnectedVideoStep components.
- Replaced native button elements with a custom Button component for consistency.
- Updated SetupAvailability to use async mutation methods.
- Integrated loading state into the onboarding view for smoother transitions between steps.
This commit is contained in:
Ayush Agarwal
2025-03-25 18:13:09 +00:00
committed by GitHub
parent 0772e1d168
commit ecd85cd15a
4 changed files with 27 additions and 18 deletions
@@ -1,7 +1,7 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Icon } from "@calcom/ui/components/icon";
import { Button } from "@calcom/ui/components/button";
import { List } from "@calcom/ui/components/list";
import { AppConnectionItem } from "../components/AppConnectionItem";
@@ -11,10 +11,11 @@ import { StepConnectionLoader } from "../components/StepConnectionLoader";
interface IConnectCalendarsProps {
nextStep: () => void;
isPageLoading: boolean;
}
const ConnectedCalendars = (props: IConnectCalendarsProps) => {
const { nextStep } = props;
const { nextStep, isPageLoading } = props;
const queryConnectedCalendars = trpc.viewer.connectedCalendars.useQuery({
onboarding: true,
eventTypeId: null,
@@ -81,18 +82,18 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
{queryIntegrations.isPending && <StepConnectionLoader />}
<button
type="button"
<Button
EndIcon="arrow-right"
data-testid="save-calendar-button"
className={classNames(
"text-inverted bg-inverted border-inverted mt-8 flex w-full flex-row justify-center rounded-md border p-2 text-center text-sm",
disabledNextButton ? "cursor-not-allowed opacity-20" : ""
)}
loading={isPageLoading}
onClick={() => nextStep()}
disabled={disabledNextButton}>
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</button>
</Button>
</>
);
};
@@ -3,7 +3,7 @@ import { userMetadata } from "@calcom/prisma/zod-utils";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import classNames from "@calcom/ui/classNames";
import { Icon } from "@calcom/ui/components/icon";
import { Button } from "@calcom/ui/components/button";
import { List } from "@calcom/ui/components/list";
import { AppConnectionItem } from "../components/AppConnectionItem";
@@ -11,10 +11,11 @@ import { StepConnectionLoader } from "../components/StepConnectionLoader";
interface ConnectedAppStepProps {
nextStep: () => void;
isPageLoading: boolean;
}
const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
const { nextStep } = props;
const { nextStep, isPageLoading } = props;
const { data: queryConnectedVideoApps, isPending } = trpc.viewer.apps.integrations.useQuery({
variant: "conferencing",
onlyInstalled: false,
@@ -70,18 +71,18 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
)}
{isPending && <StepConnectionLoader />}
<button
type="button"
<Button
EndIcon="arrow-right"
data-testid="save-video-button"
className={classNames(
"text-inverted border-inverted bg-inverted mt-8 flex w-full flex-row justify-center rounded-md border p-2 text-center text-sm",
!hasAnyInstalledVideoApps ? "cursor-not-allowed opacity-20" : ""
)}
disabled={!hasAnyInstalledVideoApps}
loading={isPageLoading}
onClick={() => nextStep()}>
{t("next_step_text")}
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</button>
</Button>
</>
);
};
@@ -51,13 +51,13 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
handleSubmit={async (values) => {
try {
if (defaultScheduleId) {
await updateSchedule.mutate({
await updateSchedule.mutateAsync({
scheduleId: defaultScheduleId,
name: t("default_schedule_name"),
...values,
});
} else {
await createSchedule.mutate({
await createSchedule.mutateAsync({
name: t("default_schedule_name"),
...values,
});
@@ -3,7 +3,7 @@
import type { TFunction } from "i18next";
import { signOut } from "next-auth/react";
import { usePathname, useRouter } from "next/navigation";
import { Suspense } from "react";
import { Suspense, useTransition } from "react";
import { Toaster } from "sonner";
import { z } from "zod";
@@ -93,6 +93,7 @@ const OnboardingPage = (props: PageProps) => {
const router = useRouter();
const [user] = trpc.viewer.me.get.useSuspenseQuery();
const { t } = useLocale();
const [isNextStepLoading, startTransition] = useTransition();
const result = stepRouteSchema.safeParse({
...params,
@@ -126,7 +127,9 @@ const OnboardingPage = (props: PageProps) => {
const goToNextStep = () => {
const nextIndex = currentStepIndex + 1;
const newStep = steps[nextIndex];
router.push(`/getting-started/${stepTransform(newStep)}`);
startTransition(() => {
router.push(`/getting-started/${stepTransform(newStep)}`);
});
};
return (
@@ -161,9 +164,13 @@ const OnboardingPage = (props: PageProps) => {
{currentStep === "user-settings" && (
<UserSettings nextStep={goToNextStep} hideUsername={from === "signup"} />
)}
{currentStep === "connected-calendar" && <ConnectedCalendars nextStep={goToNextStep} />}
{currentStep === "connected-calendar" && (
<ConnectedCalendars nextStep={goToNextStep} isPageLoading={isNextStepLoading} />
)}
{currentStep === "connected-video" && <ConnectedVideoStep nextStep={goToNextStep} />}
{currentStep === "connected-video" && (
<ConnectedVideoStep nextStep={goToNextStep} isPageLoading={isNextStepLoading} />
)}
{currentStep === "setup-availability" && (
<SetupAvailability nextStep={goToNextStep} defaultScheduleId={user.defaultScheduleId} />