Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
bba80ad
feat(definitions): add widget-integration map and default configs
ajnart May 1, 2026
b6615fb
feat(db): seed default board, home board, and bookmark apps
ajnart May 1, 2026
9e3b495
feat(api): add integrations onboarding step with dashboard population
ajnart May 1, 2026
e3fb857
feat(ui): add reusable integration select grid and modal components
ajnart May 1, 2026
7985ff9
feat(onboarding): add integration configuration stepper
ajnart May 1, 2026
1c6782a
feat(integrations): add grid picker page and dashboard integration ac…
ajnart May 1, 2026
e0c2978
fix: resolve CI failures from integrations onboarding step
ajnart May 1, 2026
62d5716
chore: format
ajnart May 1, 2026
b8b2097
fix(onboarding): fix nextOnboardingStepAsync
ajnart May 1, 2026
5a0ba98
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 4, 2026
6520ded
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 7, 2026
8fc7587
fix(definitions): correct featured integrations and default configs
ajnart May 14, 2026
9c0e739
fix(i18n): add translation keys for integration grid categories and w…
ajnart May 14, 2026
bfa2295
refactor(ui): extract shared integration grid logic and deduplicate
ajnart May 14, 2026
3450036
fix(ui): add custom xxl modal size to prevent overflow on small screens
ajnart May 14, 2026
947504a
fix(onboarding): replace stepper with progress bar and fix finish flow
ajnart May 14, 2026
97e39ce
refactor(integrations): make onSuccess required and simplify new page
ajnart May 14, 2026
bef2758
fix(api): refactor onboard-router widget placement and step advancement
ajnart May 14, 2026
904bef5
fix(ui): use inline vars resolver instead of Modal.extend
ajnart May 14, 2026
c7300ae
fix(onboarding): preserve selected integrations when navigating back
ajnart May 14, 2026
c3523ad
feat(onboarding): add skip button to integration configuration step
ajnart May 14, 2026
16091ff
fix(onboarding): use consistent card width across all integration phases
ajnart May 14, 2026
c8ff718
fix(definitions): skip timetable widget from default onboarding dashb…
ajnart May 14, 2026
8de4437
fix(ui): use default variant for skip button in integration form
ajnart May 14, 2026
6364e41
fix(onboarding): add error recovery for setupIntegrations failure
ajnart May 14, 2026
dd3cb39
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 16, 2026
545a10a
fix: remove timetable from defaultWidgetConfigs
ajnart May 16, 2026
7111099
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 18, 2026
5bc88eb
fix: MissingSecretError type mismatch in onboard createIntegration mu…
ajnart May 18, 2026
e714c96
chore: fix formatting
ajnart May 18, 2026
9377e4b
Merge remote-tracking branch 'origin/dev' into feat/add-integration-f…
ajnart May 25, 2026
1fcc149
feat(seed): add demo user seeding with pre-placed widgets on dashboar…
ajnart May 25, 2026
309ba05
feat(onboarding): enhance integration setup with Docker discovery, UR…
ajnart May 27, 2026
c97513a
chore: trigger deployment
ajnart May 27, 2026
a6566f7
fix: address PR review feedback
ajnart May 27, 2026
1df84cb
feat(onboarding): hide 0-widget integrations and add selectable app grid
ajnart May 27, 2026
18db344
fix(security): replace regex with loop to prevent ReDoS
ajnart May 27, 2026
6461d84
fix(ui): use Modal.extend for proper Mantine v7 typing
ajnart May 27, 2026
1b19248
fix(ui): revert Modal.extend, not available at runtime
ajnart May 27, 2026
51004f0
revert changes
ajnart May 27, 2026
d571396
fix(onboarding): discover Docker apps even without icon match
ajnart May 27, 2026
4608753
fix(onboarding): fallback to CDN icon URL when icons table is empty
ajnart May 27, 2026
a89479e
fix(onboarding): strict Docker discovery — only running containers wi…
ajnart May 27, 2026
4d801aa
feat(onboarding): add Docker scanning loading animation and fix hydra…
ajnart May 27, 2026
0efbe8f
style: format onboard-router with oxfmt
ajnart May 27, 2026
710187f
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 27, 2026
8cb3c9d
style: auto-fix lint and format
ajnart May 27, 2026
d0f63b9
fix(seed): use 10 columns for default dashboard layout
ajnart May 28, 2026
095d40b
fix(ui): prevent item select grid from collapsing with few results
ajnart May 28, 2026
10c2e42
fix(ui): enforce minimum width on item select modal to prevent resizing
ajnart May 28, 2026
2268aa3
fix(ui): enforce minimum width on all grid-based select modals
ajnart May 28, 2026
0e24411
Merge branch 'dev' into feat/add-integration-from-dashboard
ajnart May 28, 2026
1065e30
feat(init): unify onboarding step widths with shared layout
ajnart May 28, 2026
3ff3d41
fix(init): improve integrations step UX
ajnart May 28, 2026
efc48e5
feat(definitions): add apiKeySettingsPath for qBittorrent
ajnart May 28, 2026
bbb04b0
fix(definitions): use HTTPS for subdomain URL template
ajnart May 28, 2026
7c06536
feat(init): apply base URL template to discovered apps
ajnart May 28, 2026
28f3124
style: auto-fix lint and format
ajnart May 28, 2026
1c9a63d
fix: address CodeQL and code quality review comments
ajnart May 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 1 addition & 9 deletions apps/nextjs/src/app/[locale]/_client-providers/mantine.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,7 @@ export const CustomMantineProvider = ({
const manager = useColorSchemeManager();
return (
<DirectionProvider>
<MantineProvider
defaultColorScheme={defaultColorScheme}
colorSchemeManager={manager}
theme={createTheme({
primaryColor: "red",
autoContrast: true,
defaultRadius: "sm",
})}
>
<MantineProvider defaultColorScheme={defaultColorScheme} colorSchemeManager={manager} theme={theme}>
{children}
</MantineProvider>
</DirectionProvider>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
IconLayoutBoard,
IconPencil,
IconPencilOff,
IconPlug,
IconPlus,
IconReplace,
IconResize,
Expand All @@ -36,6 +37,7 @@ import { useBoardPermissions } from "~/components/board/permissions/client";
import { useCategoryActions } from "~/components/board/sections/category/category-actions";
import { CategoryEditModal } from "~/components/board/sections/category/category-edit-modal";
import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dynamic-actions";
import { IntegrationSelectModal } from "~/components/integration/integration-select-modal";
import { HeaderButton } from "~/components/layout/header/button";

export const BoardContentHeaderActions = () => {
Expand Down Expand Up @@ -67,6 +69,7 @@ const AddMenu = () => {
const { openModal: openCategoryEditModal } = useModalAction(CategoryEditModal);
const { openModal: openItemSelectModal } = useModalAction(ItemSelectModal);
const { openModal: openAppSelectModal } = useModalAction(AppSelectModal);
const { openModal: openIntegrationSelectModal } = useModalAction(IntegrationSelectModal);
const { addCategoryToEnd } = useCategoryActions();
const { addDynamicSection } = useDynamicSectionActions();
const { createItem } = useItemActions();
Expand Down Expand Up @@ -108,6 +111,10 @@ const AddMenu = () => {
});
}, [openAppSelectModal, createItem]);

const handleAddIntegration = useCallback(() => {
openIntegrationSelectModal({});
}, [openIntegrationSelectModal]);

return (
<Menu position="bottom-end">
<Menu.Target>
Expand All @@ -127,6 +134,10 @@ const AddMenu = () => {
{t("app.action.add")}
</Menu.Item>

<Menu.Item leftSection={<IconPlug size={20} />} onClick={handleAddIntegration}>
{t("integration.action.create")}
</Menu.Item>

<Menu.Divider />

<Menu.Item leftSection={<IconBoxAlignTop size={20} />} onClick={handleAddCategory}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { PropsWithChildren } from "react";
import { Card } from "@mantine/core";

interface InitStepCardProps {
withBorder?: boolean;
}

export const InitStepCard = ({ children, withBorder = true }: PropsWithChildren<InitStepCardProps>) => (
<Card w="100%" withBorder={withBorder}>
{children}
</Card>
);
2 changes: 2 additions & 0 deletions apps/nextjs/src/app/[locale]/init/_constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const onboardingContentWidth = 64 * 16;
export const onboardingMaxWidth = "90vw";
30 changes: 9 additions & 21 deletions apps/nextjs/src/app/[locale]/init/_steps/finish/init-finish.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,27 @@
import type { MantineColor } from "@mantine/core";
import { Button, Card, Stack, Text } from "@mantine/core";
import { IconBook2, IconCategoryPlus, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react";
import { Button, Stack, Text } from "@mantine/core";
import { IconBook2, IconLayoutDashboard, IconMailForward } from "@tabler/icons-react";

import { isProviderEnabled } from "@homarr/auth/server";
import { getMantineColor } from "@homarr/common";
import { db } from "@homarr/db";
import { createDocumentationLink } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { Link } from "@homarr/ui";
import type { TablerIcon } from "@homarr/ui";

import { InitStepCard } from "../../_components/init-step-card";

export const InitFinish = async () => {
const firstBoard = await db.query.boards.findFirst({ columns: { name: true } });
const tFinish = await getScopedI18n("init.step.finish");

return (
<Card w={64 * 6} maw="90vw">
<InitStepCard>
<Stack>
<Text>{tFinish("description")}</Text>

{firstBoard ? (
<InternalLinkButton
href={`/auth/login?callbackUrl=/boards/${firstBoard.name}`}
iconProps={{ icon: IconLayoutDashboard, color: "blue" }}
>
{tFinish("action.goToBoard", { name: firstBoard.name })}
</InternalLinkButton>
) : (
<InternalLinkButton
href="/auth/login?callbackUrl=/manage/boards"
iconProps={{ icon: IconCategoryPlus, color: "blue" }}
>
{tFinish("action.createBoard")}
</InternalLinkButton>
)}
<InternalLinkButton href="/auth/login?callbackUrl=/" iconProps={{ icon: IconLayoutDashboard, color: "blue" }}>
{tFinish("action.goToBoard", { name: "dashboard" })}
</InternalLinkButton>

{isProviderEnabled("credentials") && (
<InternalLinkButton
Expand All @@ -51,7 +39,7 @@ export const InitFinish = async () => {
{tFinish("action.docs")}
</ExternalLinkButton>
</Stack>
</Card>
</InitStepCard>
);
};

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use client";

import { Button, Card, Stack, TextInput } from "@mantine/core";
import { Button, Stack, TextInput } from "@mantine/core";
import { IconArrowRight } from "@tabler/icons-react";
import type { z } from "zod/v4";

Expand All @@ -10,6 +10,8 @@ import { useZodForm } from "@homarr/form";
import { useI18n } from "@homarr/translation/client";
import { groupCreateSchema } from "@homarr/validation/group";

import { InitStepCard } from "../../_components/init-step-card";

export const InitGroup = () => {
const t = useI18n();
const { mutateAsync } = clientApi.group.createInitialExternalGroup.useMutation({
Expand All @@ -34,7 +36,7 @@ export const InitGroup = () => {
};

return (
<Card w={64 * 6} maw="90vw">
<InitStepCard>
<form onSubmit={form.onSubmit(handleSubmitAsync)}>
<Stack>
<TextInput
Expand All @@ -48,6 +50,6 @@ export const InitGroup = () => {
</Button>
</Stack>
</form>
</Card>
</InitStepCard>
);
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { ActionIcon, Button, Card, Group, Text } from "@mantine/core";
import { ActionIcon, Button, Group, Text } from "@mantine/core";
import type { FileWithPath } from "@mantine/dropzone";
import { IconPencil } from "@tabler/icons-react";

import { humanFileSize } from "@homarr/common";
import { useScopedI18n } from "@homarr/translation/client";

import { InitStepCard } from "../../_components/init-step-card";

interface FileInfoCardProps {
file: FileWithPath;
onRemove: () => void;
Expand All @@ -13,7 +15,7 @@ interface FileInfoCardProps {
export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
const tFileInfo = useScopedI18n("init.step.import.fileInfo");
return (
<Card w={64 * 12 + 8} maw="90vw" withBorder={false}>
<InitStepCard withBorder={false}>
<Group justify="space-between" align="center" wrap="nowrap">
<Group>
<Text fw={500} lineClamp={1} style={{ wordBreak: "break-all" }}>
Expand All @@ -36,6 +38,6 @@ export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => {
<IconPencil size={16} stroke={1.5} />
</ActionIcon>
</Group>
</Card>
</InitStepCard>
);
};
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
"use client";

import { startTransition, useState } from "react";
import { Card, Stack } from "@mantine/core";
import { Stack } from "@mantine/core";
import type { FileWithPath } from "@mantine/dropzone";

import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
import { InitialOldmarrImport } from "@homarr/old-import/components";

import { InitStepCard } from "../../_components/init-step-card";
import { FileInfoCard } from "./file-info-card";
import { ImportDropZone } from "./import-dropzone";

Expand All @@ -20,7 +21,7 @@ export const InitImport = () => {

if (!file) {
return (
<Card w={64 * 12 + 8} maw="90vw">
<InitStepCard>
<ImportDropZone
loading={isPending}
updateFile={(file) => {
Expand All @@ -40,12 +41,12 @@ export const InitImport = () => {
});
}}
/>
</Card>
</InitStepCard>
);
}

return (
<Stack mb="sm">
<Stack w="100%" mb="sm">
<FileInfoCard file={file} onRemove={() => setFile(null)} />
{analyseResult !== null && <InitialOldmarrImport file={file} analyseResult={analyseResult} />}
</Stack>
Expand Down
Loading