Merge origin/release/v2 into feat/fix-v2-stack-review-findings

This commit is contained in:
ajnart committed 2026-08-21 19:08:02 +02:00
commit e1775e524b
583 files changed
+16433 -9067

No files matched your search

+5 -5
View File
@@ -11,7 +11,7 @@ import { revalidatePathActionAsync } from "@homarr/common/client";
import { useZodForm } from "@homarr/form";
import { UserCreatePasswordFields } from "@homarr/forms-collection";
import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { groupCreateSchema } from "@homarr/validation/group";
import { userInitSchema } from "@homarr/validation/user";
@@ -41,7 +41,7 @@ export const AccountSetup = ({ environment }: OnboardingStudioProps) => {
};
const CredentialsSetup = () => {
const t = useScopedI18n("init.studio.account");
const t = useI18n("init.studio.account");
const [requiresSignIn, setRequiresSignIn] = useState(false);
const [submitError, setSubmitError] = useState<string | null>(null);
const recoveryActionRef = useRef<HTMLAnchorElement>(null);
@@ -104,7 +104,7 @@ const CredentialsSetup = () => {
return (
<AccountShell title={t("title")} description={t("description")}>
<form onSubmit={form.onSubmit((values) => void submitAsync(values))}>
<form onSubmit={form.onSubmit((values) => submitAsync(values))}>
<Stack gap="md">
<TextInput label={t("username")} autoComplete="username" withAsterisk {...form.getInputProps("username")} />
<UserCreatePasswordFields
@@ -119,7 +119,7 @@ const CredentialsSetup = () => {
{submitError}
</Alert>
) : null}
<Button type="submit" size="md" loading={mutation.isPending} rightSection={<IconArrowRight size={18} />}>
<Button type="submit" size="md" loading={form.submitting} rightSection={<IconArrowRight size={18} />}>
{t("create")}
</Button>
</Stack>
@@ -129,7 +129,7 @@ const CredentialsSetup = () => {
};
const ExternalGroupSetup = () => {
const t = useScopedI18n("init.studio.externalGroup");
const t = useI18n("init.studio.externalGroup");
const [submitError, setSubmitError] = useState<string | null>(null);
const sounds = useOnboardingSounds();
const mutation = clientApi.group.createInitialExternalGroup.useMutation({
+2 -2
View File
@@ -6,7 +6,7 @@ import { IconArrowRight, IconBook2, IconSparkles, IconTool } from "@tabler/icons
import confetti from "canvas-confetti";
import { createDocumentationLink } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import { OnboardingBackdrop } from "./onboarding-backdrop";
@@ -16,7 +16,7 @@ import { useOnboardingSounds } from "./use-onboarding-sounds";
import classes from "./onboarding-studio.module.css";
export const Finish = ({ environment }: OnboardingStudioProps) => {
const t = useScopedI18n("init.studio.finish");
const t = useI18n("init.studio.finish");
const sounds = useOnboardingSounds();
const primaryColor = environment.initialBoard?.primaryColor ?? "#fa5252";
const secondaryColor = environment.initialBoard?.secondaryColor ?? "#fd7e14";
@@ -4,7 +4,7 @@ import type { ReactNode } from "react";
import { useEffect, useState } from "react";
import { Alert, Button, Center, Paper, Stack, Text, Title } from "@mantine/core";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { Link } from "@homarr/ui";
import type { OnboardingStudioProps } from "./types";
@@ -22,8 +22,8 @@ const getInitialClaimState = (accessState: ReturnType<typeof getOnboardingAccess
};
export const OnboardingStudio = (props: OnboardingStudioProps) => {
const t = useScopedI18n("init.studio.claim");
const tCommon = useScopedI18n("common.action");
const t = useI18n("init.studio.claim");
const tCommon = useI18n("common.action");
const accessState = getOnboardingAccessState(props.environment);
const [claimState, setClaimState] = useState<ClaimState>(() => getInitialClaimState(accessState));
const [claimError, setClaimError] = useState<string | null>(null);
@@ -5,7 +5,7 @@ import { useRef, useState } from "react";
import { FloatingIndicator, Stack, TextInput, UnstyledButton } from "@mantine/core";
import type { UrlTemplateMode } from "@homarr/definitions";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { useOnboardingSounds } from "./use-onboarding-sounds";
import classes from "./onboarding-studio.module.css";
@@ -78,7 +78,7 @@ export const ServiceUrlTemplate = ({
readOnly,
required,
}: ServiceUrlTemplateProps) => {
const t = useScopedI18n("common.serviceUrlTemplate");
const t = useI18n("common.serviceUrlTemplate");
return (
<Stack gap="sm">
+18 -17
View File
@@ -90,7 +90,7 @@ import {
} from "@homarr/definitions";
import { showErrorNotification, showSuccessNotification, showWarningNotification } from "@homarr/notifications";
import type { SupportedLanguage } from "@homarr/translation";
import { useCurrentLocale, useScopedI18n } from "@homarr/translation/client";
import { useCurrentLocale, useI18n } from "@homarr/translation/client";
import { BoardColorInput, ColorSchemeCombobox, IntegrationAvatar, LanguageCombobox, Link } from "@homarr/ui";
import { IntegrationMultiSelectGrid } from "@homarr/ui/integration-select-grid";
@@ -154,8 +154,8 @@ const emptyDiscoveredIntegrations: DockerDiscoveryData["integrations"] = [];
const emptyDiscoveredApps: DockerDiscoveryData["apps"] = [];
export const SetupStudio = ({ environment, assistantConfiguration }: OnboardingStudioProps) => {
const t = useScopedI18n("init.studio");
const tCommon = useScopedI18n("common.action");
const t = useI18n("init.studio");
const tCommon = useI18n("common.action");
const currentLocale = useCurrentLocale();
const { colorScheme, setColorScheme } = useMantineColorScheme();
const sounds = useOnboardingSounds();
@@ -781,7 +781,7 @@ export const SetupStudio = ({ environment, assistantConfiguration }: OnboardingS
rightSection={<IconArrowRight size={16} />}
onClick={continueOnboarding}
>
{t("continue")}
{tCommon("continue")}
</Button>
</Group>
)}
@@ -887,7 +887,7 @@ const SectionHeading = ({
);
const Essentials = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.essentials");
const t = useI18n("init.studio.essentials");
const [isHydrated, setIsHydrated] = useState(false);
useEffect(() => setIsHydrated(true), []);
return (
@@ -936,7 +936,7 @@ const Essentials = (props: StudioSectionProps) => {
};
const Discovery = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.discover");
const t = useI18n("init.studio.discover");
const dockerStatus = !props.environment.dockerConfigured
? "disabled"
: props.docker.isPending
@@ -1064,7 +1064,7 @@ const CapabilityRow = ({
detail: string;
action?: ReactNode;
}) => {
const t = useScopedI18n("init.studio.discover.status");
const t = useI18n("init.studio.discover.status");
const color =
status === "available"
? "green"
@@ -1107,7 +1107,7 @@ const CapabilityRow = ({
};
const Connections = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.connect");
const t = useI18n("init.studio.connect");
return (
<Stack gap="xl">
<SectionHeading headingRef={props.headingRef} title={t("title")} description={t("description")} />
@@ -1248,7 +1248,8 @@ const IntegrationEditor = ({
update: (patch: Partial<IntegrationDraft>) => void;
selectSecretOption: (option: number) => void;
}) => {
const t = useScopedI18n("init.studio.connect");
const t = useI18n("init.studio.connect");
const tCommon = useI18n("common.field");
const sounds = useOnboardingSounds();
const options = getAllSecretKindOptions(draft.kind);
const apiKeyUrl = getIntegrationApiKeyUrl(draft.url, draft.kind);
@@ -1313,7 +1314,7 @@ const IntegrationEditor = ({
<Stack>
<SimpleGrid cols={{ base: 1, sm: 2 }}>
<TextInput
label={t("name")}
label={tCommon("name")}
value={draft.name}
onChange={(event) => updateAndResetTest({ name: event.currentTarget.value })}
/>
@@ -1415,7 +1416,7 @@ const formatSecretKind = (kind: IntegrationSecretKind) =>
kind.replace(/([A-Z])/g, " $1").replace(/^./, (letter) => letter.toUpperCase());
const BoardBuilder = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.board");
const t = useI18n("init.studio.board");
const sounds = useOnboardingSounds();
const validationErrors = getBoardValidationErrors({
attempted: props.boardValidationAttempted,
@@ -1634,7 +1635,7 @@ const BoardPreview = ({
};
const Extensions = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.extend");
const t = useI18n("init.studio.extend");
return (
<Stack gap={0}>
<Title id="studio-section-title" ref={props.headingRef} order={2} tabIndex={-1} mb="xs">
@@ -1649,7 +1650,7 @@ const Extensions = (props: StudioSectionProps) => {
{t("assistant")}
</Tabs.Tab>
<Tabs.Tab value="mcp" leftSection={<IconCode size={16} />}>
{t("mcp")}
MCP
</Tabs.Tab>
</Tabs.List>
<Tabs.Panel value="workshop" pt="xs">
@@ -1673,7 +1674,7 @@ const AssistantSetup = ({
environment: StudioSectionProps["environment"];
configuration: ReactNode;
}) => {
const t = useScopedI18n("init.studio.extend.assistantSetup");
const t = useI18n("init.studio.extend.assistantSetup");
if (!environment.canConfigurePrivileged) {
return (
<Alert icon={<IconKey size={18} />} title={t("loginTitle")}>
@@ -1697,7 +1698,7 @@ const WorkshopSetup = ({
environment: StudioSectionProps["environment"];
runtimeCapabilities: StudioSectionProps["runtimeCapabilities"];
}) => {
const t = useScopedI18n("init.studio.extend.workshopSetup");
const t = useI18n("init.studio.extend.workshopSetup");
const status = runtimeCapabilities.isPending
? "checking"
: runtimeCapabilities.data?.workshop.status === "available"
@@ -1755,7 +1756,7 @@ const WorkshopFeature = ({ title, description }: { title: string; description: s
);
const McpSetup = ({ environment }: { environment: StudioSectionProps["environment"] }) => {
const t = useScopedI18n("init.studio.extend.mcpSetup");
const t = useI18n("init.studio.extend.mcpSetup");
const [apiKey, setApiKey] = useState<string | null>(null);
const sounds = useOnboardingSounds();
const createKey = clientApi.apiKeys.create.useMutation({
@@ -1861,7 +1862,7 @@ const McpSetup = ({ environment }: { environment: StudioSectionProps["environmen
};
const Review = (props: StudioSectionProps) => {
const t = useScopedI18n("init.studio.review");
const t = useI18n("init.studio.review");
const themeLabel = {
auto: t("themeAuto"),
light: t("themeLight"),
+3 -3
View File
@@ -7,7 +7,7 @@ import { IconArrowRight, IconDatabaseImport } from "@tabler/icons-react";
import { clientApi } from "@homarr/api/client";
import { revalidatePathActionAsync } from "@homarr/common/client";
import { showErrorNotification } from "@homarr/notifications";
import { useScopedI18n } from "@homarr/translation/client";
import { useI18n } from "@homarr/translation/client";
import { OnboardingBackdrop } from "./onboarding-backdrop";
import { OnboardingWordmark } from "./onboarding-wordmark";
@@ -16,8 +16,8 @@ import { useOnboardingSounds, useWelcomeSound } from "./use-onboarding-sounds";
import classes from "./onboarding-studio.module.css";
export const Welcome = ({ environment, sqliteRestore }: OnboardingStudioProps) => {
const t = useScopedI18n("init.studio.welcome");
const claimT = useScopedI18n("init.studio.claim");
const t = useI18n("init.studio.welcome");
const claimT = useI18n("init.studio.claim");
const [showRestore, setShowRestore] = useState(false);
const [claimPending, setClaimPending] = useState(false);
const [claimError, setClaimError] = useState<string | null>(null);