From 83ae98847af68eef20a3b8622131697067c4eff6 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 3 Oct 2026 22:34:05 +0200 Subject: [PATCH] fix(onboarding): return to welcome before administrator creation (#7012) --- .../after-the-installation.mdx | 4 +- .../api/src/router/onboard/onboard-router.ts | 20 +++++++++- packages/onboarding/src/account.tsx | 37 +++++++++++++++++-- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/apps/docs/docs/getting-started/after-the-installation.mdx b/apps/docs/docs/getting-started/after-the-installation.mdx index 0115e9282..2d46873df 100644 --- a/apps/docs/docs/getting-started/after-the-installation.mdx +++ b/apps/docs/docs/getting-started/after-the-installation.mdx @@ -16,7 +16,9 @@ import { } from "@tabler/icons-react"; After Homarr starts, open its URL. An unfinished installation redirects to the onboarding screen at `/init`. Choose -**Get started** for a new installation or **Restore backup** to import a compatible SQLite backup. +**Get started** for a new installation or **Restore backup** to import a compatible SQLite backup. Before creating the +administrator, you can return to Welcome with **Back**, including after refreshing `/init`. Creating the administrator +commits this setup stage; later steps do not rewind it. Onboarding writes the administrator and board configuration to the database. If onboarding returns after a restart, diff --git a/packages/api/src/router/onboard/onboard-router.ts b/packages/api/src/router/onboard/onboard-router.ts index 2b6b0b73d..339ee4d30 100644 --- a/packages/api/src/router/onboard/onboard-router.ts +++ b/packages/api/src/router/onboard/onboard-router.ts @@ -8,7 +8,7 @@ import { createId } from "@homarr/common"; import { encryptSecret } from "@homarr/common/server"; import { createLogger } from "@homarr/core/infrastructure/logs"; import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error"; -import { handleTransactionsAsync, eq, inArray, like, or } from "@homarr/db"; +import { handleTransactionsAsync, and, eq, inArray, like, notExists, or } from "@homarr/db"; import { getServerSettingByKeyAsync } from "@homarr/db/queries"; import { apps, @@ -25,6 +25,7 @@ import { sectionLayouts, sections, serverSettings, + users, } from "@homarr/db/schema"; import { emptySuperJSON, @@ -147,6 +148,23 @@ export const onboardRouter = createTRPCRouter({ await nextOnboardingStepAsync(ctx.db); }), + returnToWelcome: onboardingProcedure.requiresStep("user").mutation(async ({ ctx }) => { + const returnedRows = await ctx.db + .update(onboarding) + .set({ step: "start", previousStep: null }) + .where( + and( + eq(onboarding.step, "user"), + eq(onboarding.previousStep, "start"), + notExists(ctx.db.select({ id: users.id }).from(users)), + ), + ) + .returning({ id: onboarding.id }); + if (returnedRows.length !== 1) { + throw new TRPCError({ code: "PRECONDITION_FAILED", message: "Administrator setup can no longer be restarted." }); + } + }), + testIntegration: onboardingProcedure .requiresStep("setup") .input(onboardingIntegrationDraftSchema) diff --git a/packages/onboarding/src/account.tsx b/packages/onboarding/src/account.tsx index 0b084d168..9169c797b 100644 --- a/packages/onboarding/src/account.tsx +++ b/packages/onboarding/src/account.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from "react"; import { Alert, Button, Stack, Text, TextInput } from "@mantine/core"; -import { IconArrowRight, IconShieldCheck, IconUserPlus } from "@tabler/icons-react"; +import { IconArrowLeft, IconArrowRight, IconShieldCheck, IconUserPlus } from "@tabler/icons-react"; import type { z } from "zod/v4"; import { clientApi } from "@homarr/api/client"; @@ -36,11 +36,12 @@ const AccountShell = ({ ); export const AccountSetup = ({ environment }: OnboardingStudioProps) => { - if (environment.currentStep === "user") return ; + if (environment.currentStep === "user") return ; return ; }; -const CredentialsSetup = () => { +const CredentialsSetup = ({ canReturnToWelcome }: { canReturnToWelcome: boolean }) => { + const navigationT = useI18n("init.studio"); const t = useI18n("init.studio.account"); const [requiresSignIn, setRequiresSignIn] = useState(false); const [submitError, setSubmitError] = useState(null); @@ -54,6 +55,16 @@ const CredentialsSetup = () => { showErrorNotification({ title: t("errorTitle"), message }); }, }); + const returnToWelcome = clientApi.onboard.returnToWelcome.useMutation({ + async onSuccess() { + sounds.swoosh(); + await revalidatePathActionAsync("/init"); + }, + onError() { + sounds.error(); + showErrorNotification({ title: t("errorTitle"), message: t("unknownError") }); + }, + }); const form = useZodForm(userInitSchema, { initialValues: { username: "", password: "", confirmPassword: "" }, }); @@ -119,9 +130,27 @@ const CredentialsSetup = () => { {submitError} ) : null} - + {canReturnToWelcome && ( + + )}