feat: consolidate onboarding

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:32:12 +02:00
1 parent 8239a631f2
commit 7b1a801ab8
8 files changed
+153 -25

No files matched your search

+36 -1
View File
@@ -23,6 +23,7 @@ import {
integrationItems,
integrationUserPermissions,
integrations,
apps,
itemLayouts,
items,
layouts,
@@ -295,6 +296,11 @@ export const boardRouter = createTRPCRouter({
width: true,
height: true,
},
with: {
item: {
columns: { kind: true, options: true },
},
},
where: and(
inArray(itemLayouts.layoutId, previewLayoutIds),
inArray(itemLayouts.sectionId, rootSectionIds),
@@ -333,6 +339,27 @@ export const boardRouter = createTRPCRouter({
])
: [[], []];
const appIdByItemId = new Map(
previewItemLayouts.flatMap((layout) => {
if (layout.item.kind !== "app") return [];
try {
const { appId } = superjson.parse<{ appId?: unknown }>(layout.item.options);
return typeof appId === "string" ? [[layout.itemId, appId] as const] : [];
} catch {
return [];
}
}),
);
const previewAppIds = [...new Set(appIdByItemId.values())];
const previewApps =
previewAppIds.length > 0
? await ctx.db.query.apps.findMany({
columns: { id: true, iconUrl: true },
where: inArray(apps.id, previewAppIds),
})
: [];
const appIconUrlById = new Map(previewApps.map((app) => [app.id, app.iconUrl]));
return dbBoards.map(({ layouts: boardLayouts, sections: boardSections, ...board }) => {
const previewLayout = boardLayouts.at(0);
@@ -365,7 +392,15 @@ export const boardRouter = createTRPCRouter({
const itemPreview = previewLayout
? previewItemLayouts
.filter((layout) => layout.layoutId === previewLayout.id && isInsideRootLane(layout))
.map((layout) => ({ id: layout.itemId, layouts: [layout] }))
.map((layout) => {
const appId = appIdByItemId.get(layout.itemId);
return {
id: layout.itemId,
kind: layout.item.kind,
iconUrl: appId ? appIconUrlById.get(appId) : undefined,
layouts: [layout],
};
})
: [];
const containerPreview = previewLayout
? previewSectionLayouts
@@ -6,6 +6,7 @@ import { createId } from "@homarr/common";
import type { Database, InferInsertModel } from "@homarr/db";
import { and, eq } from "@homarr/db";
import {
apps,
boardGroupPermissions,
boards,
boardUserPermissions,
@@ -457,6 +458,7 @@ describe("getManageOverview", () => {
items: [
{
id: itemId,
kind: "clock",
layouts: [{ layoutId: baseLayoutId, sectionId: rootSectionId, width: 4, height: 2 }],
},
],
@@ -475,6 +477,64 @@ describe("getManageOverview", () => {
expect(result[0]?.preview?.sections).toHaveLength(2);
});
test("includes app and widget icons in compact previews", async () => {
const db = createDb();
const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });
const boardId = createId();
const layoutId = createId();
const rootSectionId = createId();
const appId = createId();
const appItemId = createId();
await db.insert(users).values({ id: defaultCreatorId });
await db.insert(boards).values({ id: boardId, name: "icon-overview", creatorId: defaultCreatorId });
await db.insert(layouts).values({
id: layoutId,
name: "Base",
boardId,
columnCount: 12,
breakpoint: 768,
role: "base",
});
await db.insert(sections).values({
id: rootSectionId,
boardId,
kind: "empty",
xOffset: 0,
yOffset: 0,
});
await db.insert(apps).values({
id: appId,
name: "Sonarr",
iconUrl: "https://example.com/sonarr.svg",
});
await db.insert(items).values({
id: appItemId,
boardId,
kind: "app",
options: SuperJSON.stringify({ appId }),
});
await db.insert(itemLayouts).values({
itemId: appItemId,
sectionId: rootSectionId,
layoutId,
xOffset: 0,
yOffset: 0,
width: 2,
height: 2,
});
const result = await caller.getManageOverview();
expect(result[0]?.preview?.items).toEqual([
expect.objectContaining({
id: appItemId,
kind: "app",
iconUrl: "https://example.com/sonarr.svg",
}),
]);
});
test("uses the highest-breakpoint layout when a board has no base layout", async () => {
const db = createDb();
const caller = boardRouter.createCaller({ db, deviceType: undefined, session: defaultSession });