fix: address PR review feedback
- Show error notification on setup failure instead of silent reset - Fix toggleKind stale closure by using prop array directly - Remove duplicate translations from en-gb.json (falls back to en.json) - Align seed board columnCount to 12 (codebase default) - Extract shared placeAllWidgetsAsync helper to @homarr/db/queries - Remove redundant onboardingFormSchema; reuse formSchema for both paths - Fix lint errors (optional chain, unused imports, unnecessary conditions)
This commit is contained in:
15 files changed
+233
-338
No files matched your search
@@ -14,7 +14,7 @@ import {
|
||||
ThemeIcon,
|
||||
UnstyledButton,
|
||||
} from "@mantine/core";
|
||||
import { IconChevronDown, IconBrandDocker } from "@tabler/icons-react";
|
||||
import { IconBrandDocker, IconChevronDown } from "@tabler/icons-react";
|
||||
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import { getIntegrationName } from "@homarr/definitions";
|
||||
@@ -128,21 +128,17 @@ export const DockerDiscoveryIndicator = ({
|
||||
onChange={() => onToggleAllApps(!appsEnabled)}
|
||||
styles={{ label: { fontSize: "var(--mantine-font-size-xs)" } }}
|
||||
/>
|
||||
{appsEnabled && apps.map((app) => (
|
||||
<Group key={app.containerId} gap="xs" wrap="nowrap" py={2}>
|
||||
<Avatar
|
||||
size="xs"
|
||||
radius="sm"
|
||||
src={app.iconUrl}
|
||||
styles={{ image: { objectFit: "contain" } }}
|
||||
>
|
||||
{app.containerName.at(0)?.toUpperCase()}
|
||||
</Avatar>
|
||||
<Text size="xs" c="dimmed" lineClamp={1} style={{ flex: 1, minWidth: 0 }}>
|
||||
{app.containerName}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
{appsEnabled &&
|
||||
apps.map((app) => (
|
||||
<Group key={app.containerId} gap="xs" wrap="nowrap" py={2}>
|
||||
<Avatar size="xs" radius="sm" src={app.iconUrl} styles={{ image: { objectFit: "contain" } }}>
|
||||
{app.containerName.at(0)?.toUpperCase()}
|
||||
</Avatar>
|
||||
<Text size="xs" c="dimmed" lineClamp={1} style={{ flex: 1, minWidth: 0 }}>
|
||||
{app.containerName}
|
||||
</Text>
|
||||
</Group>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -21,15 +21,14 @@ import { useDisclosure } from "@mantine/hooks";
|
||||
import { IconArrowRight, IconSettings } from "@tabler/icons-react";
|
||||
|
||||
import { clientApi } from "@homarr/api/client";
|
||||
import type { IntegrationKind } from "@homarr/definitions";
|
||||
import type { IntegrationKind, UrlTemplateMode } from "@homarr/definitions";
|
||||
import { buildIntegrationUrl, getIntegrationName } from "@homarr/definitions";
|
||||
import type { UrlTemplateMode } from "@homarr/definitions";
|
||||
import { showErrorNotification } from "@homarr/notifications";
|
||||
import { useScopedI18n } from "@homarr/translation/client";
|
||||
import { IntegrationAvatar } from "@homarr/ui";
|
||||
|
||||
import { NewIntegrationForm } from "~/app/[locale]/manage/integrations/new/_integration-new-form";
|
||||
import { IntegrationMultiSelectGrid } from "~/components/integration/integration-multi-select-grid";
|
||||
|
||||
import { DockerDiscoveryIndicator } from "./docker-discovery-panel";
|
||||
|
||||
type Phase = "select" | "configure" | "done";
|
||||
@@ -69,7 +68,7 @@ export const InitIntegrations = () => {
|
||||
|
||||
const dockerApplied = useRef(false);
|
||||
useEffect(() => {
|
||||
if (!dockerData || dockerData.status !== "success" || dockerApplied.current) return;
|
||||
if (dockerData?.status !== "success" || dockerApplied.current) return;
|
||||
dockerApplied.current = true;
|
||||
|
||||
const newDrafts = new Map(drafts);
|
||||
@@ -132,6 +131,10 @@ export const InitIntegrations = () => {
|
||||
await setupIntegrations();
|
||||
router.refresh();
|
||||
} catch {
|
||||
showErrorNotification({
|
||||
title: t("configure.error.title"),
|
||||
message: t("configure.error.message"),
|
||||
});
|
||||
setPhase("select");
|
||||
}
|
||||
};
|
||||
@@ -192,7 +195,9 @@ export const InitIntegrations = () => {
|
||||
/>
|
||||
|
||||
<Stack gap="xs">
|
||||
<Text size="sm" fw={500}>{t("urlTemplate.label")}</Text>
|
||||
<Text size="sm" fw={500}>
|
||||
{t("urlTemplate.label")}
|
||||
</Text>
|
||||
<Group gap="sm" grow>
|
||||
<TextInput
|
||||
placeholder={t("urlTemplate.placeholder")}
|
||||
@@ -216,7 +221,11 @@ export const InitIntegrations = () => {
|
||||
</Text>
|
||||
</Stack>
|
||||
|
||||
<IntegrationMultiSelectGrid selectedKinds={selectedKinds} onSelectionChange={handleSelectionChange} onboarding />
|
||||
<IntegrationMultiSelectGrid
|
||||
selectedKinds={selectedKinds}
|
||||
onSelectionChange={handleSelectionChange}
|
||||
onboarding
|
||||
/>
|
||||
|
||||
<Group justify="space-between">
|
||||
<Button variant="subtle" onClick={() => void finishSetupAsync()}>
|
||||
@@ -280,11 +289,7 @@ const ConfigurePhase = ({
|
||||
<Text fw={500}>{getIntegrationName(currentKind)}</Text>
|
||||
</Group>
|
||||
<Tooltip label={t("configure.helpers.tooltip")}>
|
||||
<ActionIcon
|
||||
variant={helpersOpen ? "light" : "subtle"}
|
||||
color="gray"
|
||||
onClick={toggleHelpers}
|
||||
>
|
||||
<ActionIcon variant={helpersOpen ? "light" : "subtle"} color="gray" onClick={toggleHelpers}>
|
||||
<IconSettings size={18} stroke={1.5} />
|
||||
</ActionIcon>
|
||||
</Tooltip>
|
||||
@@ -298,7 +303,11 @@ const ConfigurePhase = ({
|
||||
</Stack>
|
||||
|
||||
<Collapse in={helpersOpen}>
|
||||
<Stack gap="xs" p="xs" style={{ background: "var(--mantine-color-default-hover)", borderRadius: "var(--mantine-radius-sm)" }}>
|
||||
<Stack
|
||||
gap="xs"
|
||||
p="xs"
|
||||
style={{ background: "var(--mantine-color-default-hover)", borderRadius: "var(--mantine-radius-sm)" }}
|
||||
>
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("configure.helpers.description")}
|
||||
</Text>
|
||||
@@ -322,7 +331,9 @@ const ConfigurePhase = ({
|
||||
</Group>
|
||||
{baseHost && (
|
||||
<Text size="xs" c="dimmed">
|
||||
{t("urlTemplate.preview", { example: buildIntegrationUrl(currentKind, baseHost, urlMode, draft?.dockerPort ?? undefined) })}
|
||||
{t("urlTemplate.preview", {
|
||||
example: buildIntegrationUrl(currentKind, baseHost, urlMode, draft?.dockerPort ?? undefined),
|
||||
})}
|
||||
</Text>
|
||||
)}
|
||||
</Stack>
|
||||
|
||||
@@ -63,17 +63,6 @@ const formSchema = integrationCreateSchema.omit({ kind: true, app: true }).and(
|
||||
}),
|
||||
);
|
||||
|
||||
const onboardingFormSchema = z
|
||||
.object({
|
||||
name: z.string().nonempty().max(127),
|
||||
url: z.string().nonempty(),
|
||||
secrets: z.array(z.object({ kind: z.string(), value: z.string() })),
|
||||
attemptSearchEngineCreation: z.boolean(),
|
||||
hasApp: z.boolean(),
|
||||
appHref: appHrefSchema,
|
||||
appId: z.string().nullable(),
|
||||
});
|
||||
|
||||
export const NewIntegrationForm = ({
|
||||
kind,
|
||||
initialUrl,
|
||||
@@ -93,7 +82,7 @@ export const NewIntegrationForm = ({
|
||||
if (hasUrlSecret) {
|
||||
url = "http://localhost";
|
||||
}
|
||||
const form = useZodForm(isOnboarding ? onboardingFormSchema : formSchema, {
|
||||
const form = useZodForm(formSchema, {
|
||||
initialValues: {
|
||||
name: initialName ?? getIntegrationName(kind),
|
||||
url,
|
||||
|
||||
@@ -68,7 +68,9 @@ export interface IntegrationGridItem {
|
||||
widgets: WidgetKind[];
|
||||
}
|
||||
|
||||
export const buildSortedIntegrations = (options: { enableMockIntegration?: boolean; onboarding?: boolean } = {}): IntegrationGridItem[] =>
|
||||
export const buildSortedIntegrations = (
|
||||
options: { enableMockIntegration?: boolean; onboarding?: boolean } = {},
|
||||
): IntegrationGridItem[] =>
|
||||
integrationKinds
|
||||
.filter((kind) => {
|
||||
if (options.onboarding && hiddenFromOnboarding.has(kind)) return false;
|
||||
|
||||
@@ -34,18 +34,21 @@ export const IntegrationMultiSelectGrid = ({
|
||||
|
||||
const toggleKind = useCallback(
|
||||
(kind: IntegrationKind) => {
|
||||
const next = new Set(selectedKinds);
|
||||
if (next.has(kind)) {
|
||||
next.delete(kind);
|
||||
const current = new Set(selectedKindsArray);
|
||||
if (current.has(kind)) {
|
||||
current.delete(kind);
|
||||
} else {
|
||||
next.add(kind);
|
||||
current.add(kind);
|
||||
}
|
||||
onSelectionChange(Array.from(next));
|
||||
onSelectionChange(Array.from(current));
|
||||
},
|
||||
[selectedKinds, onSelectionChange],
|
||||
[selectedKindsArray, onSelectionChange],
|
||||
);
|
||||
|
||||
const integrations = useMemo(() => buildSortedIntegrations({ enableMockIntegration, onboarding }), [enableMockIntegration, onboarding]);
|
||||
const integrations = useMemo(
|
||||
() => buildSortedIntegrations({ enableMockIntegration, onboarding }),
|
||||
[enableMockIntegration, onboarding],
|
||||
);
|
||||
const filtered = useMemo(() => filterIntegrations(integrations, search), [integrations, search]);
|
||||
|
||||
return (
|
||||
|
||||
Reference in new issue
Block a user